WooCommerce PDF Invoices & Packing Slips - Version 2.5.2

Version Description

  • Fix: ImageMagick version conflict
  • Translations: Updated POT
Download this release

Release Info

Developer pomegranate
Plugin Icon 128x128 WooCommerce PDF Invoices & Packing Slips
Version 2.5.2
Comparing to
See all releases

Code changes from version 2.5.1 to 2.5.2

includes/class-wcpdf-main.php CHANGED
@@ -36,7 +36,7 @@ class Main {
36
  add_filter( 'wpo_wcpdf_document_use_historical_settings', array( $this, 'test_mode_settings' ), 15, 2 );
37
 
38
  // page numbers & currency filters
39
- add_action( 'wpo_wcpdf_get_html', array($this, 'format_page_number_placeholders' ), 10, 2 );
40
  add_action( 'wpo_wcpdf_after_dompdf_render', array($this, 'page_number_replacements' ), 9, 2 );
41
  if ( isset( WPO_WCPDF()->settings->general_settings['currency_font'] ) ) {
42
  add_action( 'wpo_wcpdf_before_pdf', array($this, 'use_currency_font' ), 10, 2 );
36
  add_filter( 'wpo_wcpdf_document_use_historical_settings', array( $this, 'test_mode_settings' ), 15, 2 );
37
 
38
  // page numbers & currency filters
39
+ add_filter( 'wpo_wcpdf_get_html', array($this, 'format_page_number_placeholders' ), 10, 2 );
40
  add_action( 'wpo_wcpdf_after_dompdf_render', array($this, 'page_number_replacements' ), 9, 2 );
41
  if ( isset( WPO_WCPDF()->settings->general_settings['currency_font'] ) ) {
42
  add_action( 'wpo_wcpdf_before_pdf', array($this, 'use_currency_font' ), 10, 2 );
includes/class-wcpdf-settings-debug.php CHANGED
@@ -1,263 +1,265 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- if ( ! defined( 'ABSPATH' ) ) {
5
- exit; // Exit if accessed directly
6
- }
7
-
8
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings_Debug' ) ) :
9
-
10
- class Settings_Debug {
11
-
12
- function __construct() {
13
- add_action( 'admin_init', array( $this, 'init_settings' ) );
14
- add_action( 'wpo_wcpdf_settings_output_debug', array( $this, 'output' ), 10, 1 );
15
- add_action( 'wpo_wcpdf_after_settings_page', array( $this, 'debug_tools' ), 10, 2 );
16
-
17
- // yes, we're hiring!
18
- if (defined('WP_DEBUG') && WP_DEBUG) {
19
- add_action( 'wpo_wcpdf_before_settings_page', array( $this, 'work_at_wpovernight' ), 10, 2 );
20
- } else {
21
- add_action( 'wpo_wcpdf_after_settings_page', array( $this, 'work_at_wpovernight' ), 30, 2 );
22
- }
23
-
24
- add_action( 'wpo_wcpdf_after_settings_page', array( $this, 'dompdf_status' ), 20, 2 );
25
- }
26
-
27
- public function output( $section ) {
28
- settings_fields( "wpo_wcpdf_settings_debug" );
29
- do_settings_sections( "wpo_wcpdf_settings_debug" );
30
-
31
- submit_button();
32
- }
33
-
34
- public function debug_tools( $tab, $section ) {
35
- if ($tab !== 'debug') {
36
- return;
37
- }
38
- ?>
39
- <form method="post">
40
- <?php wp_nonce_field( 'wpo_wcpdf_debug_tools_action', 'security' ); ?>
41
- <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="install_fonts">
42
- <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Reinstall fonts', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
43
- <?php
44
- if ( !empty($_POST) && isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'install_fonts' ) {
45
- // check permissions
46
- if ( !check_admin_referer( 'wpo_wcpdf_debug_tools_action', 'security' ) ) {
47
- return;
48
- }
49
-
50
- $font_path = WPO_WCPDF()->main->get_tmp_path( 'fonts' );
51
-
52
- // clear folder first
53
- if ( function_exists("glob") && $files = glob( $font_path.'/*.*' ) ) {
54
- $exclude_files = array( 'index.php', '.htaccess' );
55
- foreach($files as $file) {
56
- if( is_file($file) && !in_array( basename($file), $exclude_files ) ) {
57
- unlink($file);
58
- }
59
- }
60
- }
61
-
62
- WPO_WCPDF()->main->copy_fonts( $font_path );
63
- printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Fonts reinstalled!', 'woocommerce-pdf-invoices-packing-slips' ) );
64
- }
65
- ?>
66
- </form>
67
- <form method="post">
68
- <?php wp_nonce_field( 'wpo_wcpdf_debug_tools_action', 'security' ); ?>
69
- <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="clear_tmp">
70
- <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Remove temporary files', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
71
- <?php
72
- if ( !empty($_POST) && isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'clear_tmp' ) {
73
- // check permissions
74
- if ( !check_admin_referer( 'wpo_wcpdf_debug_tools_action', 'security' ) ) {
75
- return;
76
- }
77
- $tmp_path = WPO_WCPDF()->main->get_tmp_path('attachments');
78
-
79
- if ( !function_exists("glob") ) {
80
- // glob is disabled
81
- printf('<div class="notice notice-error"><p>%s<br><code>%s</code></p></div>', __( "Unable to read temporary folder contents!", 'woocommerce-pdf-invoices-packing-slips' ), $tmp_path);
82
- } else {
83
- $success = 0;
84
- $error = 0;
85
- if ( $files = glob($tmp_path.'*.pdf') ) { // get all pdf files
86
- foreach($files as $file) {
87
- if(is_file($file)) {
88
- // delete file
89
- if ( unlink($file) === true ) {
90
- $success++;
91
- } else {
92
- $error++;
93
- }
94
- }
95
- }
96
-
97
- if ($error > 0) {
98
- $message = sprintf( __( 'Unable to delete %d files! (deleted %d)', 'woocommerce-pdf-invoices-packing-slips' ), $error, $success);
99
- printf('<div class="notice notice-error"><p>%s</p></div>', $message);
100
- } else {
101
- $message = sprintf( __( 'Successfully deleted %d files!', 'woocommerce-pdf-invoices-packing-slips' ), $success );
102
- printf('<div class="notice notice-success"><p>%s</p></div>', $message);
103
- }
104
- } else {
105
- printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Nothing to delete!', 'woocommerce-pdf-invoices-packing-slips' ) );
106
- }
107
- }
108
- }
109
- ?>
110
- </form>
111
- <form method="post">
112
- <?php wp_nonce_field( 'wpo_wcpdf_debug_tools_action', 'security' ); ?>
113
- <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="delete_legacy_settings">
114
- <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Delete legacy (1.X) settings', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
115
- <?php
116
- if ( !empty($_POST) && isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'delete_legacy_settings' ) {
117
- // check permissions
118
- if ( !check_admin_referer( 'wpo_wcpdf_debug_tools_action', 'security' ) ) {
119
- return;
120
- }
121
- // delete options
122
- delete_option( 'wpo_wcpdf_general_settings' );
123
- delete_option( 'wpo_wcpdf_template_settings' );
124
- delete_option( 'wpo_wcpdf_debug_settings' );
125
- // and delete cache of these options, just in case...
126
- wp_cache_delete( 'wpo_wcpdf_general_settings','options' );
127
- wp_cache_delete( 'wpo_wcpdf_template_settings','options' );
128
- wp_cache_delete( 'wpo_wcpdf_debug_settings','options' );
129
-
130
- printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Legacy settings deleted!', 'woocommerce-pdf-invoices-packing-slips' ) );
131
- }
132
- ?>
133
- </form>
134
- <?php
135
- }
136
-
137
- public function work_at_wpovernight( $tab, $section ) {
138
- if ($tab === 'debug') {
139
- include( WPO_WCPDF()->plugin_path() . '/includes/views/work-at-wpovernight.php' );
140
- }
141
- }
142
-
143
- public function dompdf_status( $tab, $section ) {
144
- if ($tab === 'debug') {
145
- include( WPO_WCPDF()->plugin_path() . '/includes/views/dompdf-status.php' );
146
- }
147
- }
148
-
149
- public function init_settings() {
150
- // Register settings.
151
- $page = $option_group = $option_name = 'wpo_wcpdf_settings_debug';
152
-
153
- $settings_fields = array(
154
- array(
155
- 'type' => 'section',
156
- 'id' => 'debug_settings',
157
- 'title' => __( 'Debug settings', 'woocommerce-pdf-invoices-packing-slips' ),
158
- 'callback' => 'section',
159
- ),
160
- array(
161
- 'type' => 'setting',
162
- 'id' => 'legacy_mode',
163
- 'title' => __( 'Legacy mode', 'woocommerce-pdf-invoices-packing-slips' ),
164
- 'callback' => 'checkbox',
165
- 'section' => 'debug_settings',
166
- 'args' => array(
167
- 'option_name' => $option_name,
168
- 'id' => 'legacy_mode',
169
- 'description' => __( "Legacy mode ensures compatibility with templates and filters from previous versions.", 'woocommerce-pdf-invoices-packing-slips' ),
170
- )
171
- ),
172
- array(
173
- 'type' => 'setting',
174
- 'id' => 'guest_access',
175
- 'title' => __( 'Allow guest access', 'woocommerce-pdf-invoices-packing-slips' ),
176
- 'callback' => 'checkbox',
177
- 'section' => 'debug_settings',
178
- 'args' => array(
179
- 'option_name' => $option_name,
180
- 'id' => 'guest_access',
181
- 'description' => __( 'Enable this to allow customers that purchase without an account to access their PDF with a unique key', 'woocommerce-pdf-invoices-packing-slips' ),
182
- )
183
- ),
184
- array(
185
- 'type' => 'setting',
186
- 'id' => 'calculate_document_numbers',
187
- 'title' => __( 'Calculate document numbers (slow)', 'woocommerce-pdf-invoices-packing-slips' ),
188
- 'callback' => 'checkbox',
189
- 'section' => 'debug_settings',
190
- 'args' => array(
191
- 'option_name' => $option_name,
192
- 'id' => 'calculate_document_numbers',
193
- 'description' => __( "Document numbers (such as invoice numbers) are generated using AUTO_INCREMENT by default. Use this setting if your database auto increments with more than 1.", 'woocommerce-pdf-invoices-packing-slips' ),
194
- )
195
- ),
196
- array(
197
- 'type' => 'setting',
198
- 'id' => 'enable_debug',
199
- 'title' => __( 'Enable debug output', 'woocommerce-pdf-invoices-packing-slips' ),
200
- 'callback' => 'checkbox',
201
- 'section' => 'debug_settings',
202
- 'args' => array(
203
- 'option_name' => $option_name,
204
- 'id' => 'enable_debug',
205
- 'description' => __( "Enable this option to output plugin errors if you're getting a blank page or other PDF generation issues", 'woocommerce-pdf-invoices-packing-slips' ) . '<br>' .
206
- __( '<b>Caution!</b> This setting may reveal errors (from other plugins) in other places on your site too, therefor this is not recommended to leave it enabled on live sites.', 'woocommerce-pdf-invoices-packing-slips' ),
207
- )
208
- ),
209
- array(
210
- 'type' => 'setting',
211
- 'id' => 'enable_cleanup',
212
- 'title' => __( 'Enable automatic cleanup', 'woocommerce-pdf-invoices-packing-slips' ),
213
- 'callback' => 'checkbox_text_input',
214
- 'section' => 'debug_settings',
215
- 'args' => array(
216
- 'option_name' => $option_name,
217
- 'id' => 'enable_cleanup',
218
- 'disabled' => ( !function_exists("glob") || !function_exists('filemtime') ) ? 1 : NULL,
219
- 'text_input_wrap' => __( "every %s days", 'woocommerce-pdf-invoices-packing-slips' ),
220
- 'text_input_size' => 4,
221
- 'text_input_id' => 'cleanup_days',
222
- 'text_input_default'=> 7,
223
- 'description' => ( function_exists("glob") && function_exists('filemtime') ) ?
224
- __( "Automatically clean up PDF files stored in the temporary folder (used for email attachments)", 'woocommerce-pdf-invoices-packing-slips' ) :
225
- __( '<b>Disabled:</b> The PHP functions glob and filemtime are required for automatic cleanup but not enabled on your server.', 'woocommerce-pdf-invoices-packing-slips' ),
226
- )
227
- ),
228
- array(
229
- 'type' => 'setting',
230
- 'id' => 'html_output',
231
- 'title' => __( 'Output to HTML', 'woocommerce-pdf-invoices-packing-slips' ),
232
- 'callback' => 'checkbox',
233
- 'section' => 'debug_settings',
234
- 'args' => array(
235
- 'option_name' => $option_name,
236
- 'id' => 'html_output',
237
- 'description' => __( 'Send the template output as HTML to the browser instead of creating a PDF.', 'woocommerce-pdf-invoices-packing-slips' ),
238
- )
239
- ),
240
- array(
241
- 'type' => 'setting',
242
- 'id' => 'use_html5_parser',
243
- 'title' => __( 'Use alternative HTML5 parser to parse HTML', 'woocommerce-pdf-invoices-packing-slips' ),
244
- 'callback' => 'checkbox',
245
- 'section' => 'debug_settings',
246
- 'args' => array(
247
- 'option_name' => $option_name,
248
- 'id' => 'use_html5_parser',
249
- )
250
- ),
251
- );
252
-
253
- // allow plugins to alter settings fields
254
- $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_debug', $settings_fields, $page, $option_group, $option_name );
255
- WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
256
- return;
257
- }
258
-
259
- }
260
-
261
- endif; // class_exists
262
-
 
 
263
  return new Settings_Debug();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit; // Exit if accessed directly
6
+ }
7
+
8
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings_Debug' ) ) :
9
+
10
+ class Settings_Debug {
11
+
12
+ function __construct() {
13
+ add_action( 'admin_init', array( $this, 'init_settings' ) );
14
+ add_action( 'wpo_wcpdf_settings_output_debug', array( $this, 'output' ), 10, 1 );
15
+ add_action( 'wpo_wcpdf_after_settings_page', array( $this, 'debug_tools' ), 10, 2 );
16
+
17
+ // yes, we're hiring!
18
+ if (defined('WP_DEBUG') && WP_DEBUG) {
19
+ add_action( 'wpo_wcpdf_before_settings_page', array( $this, 'work_at_wpovernight' ), 10, 2 );
20
+ } else {
21
+ add_action( 'wpo_wcpdf_after_settings_page', array( $this, 'work_at_wpovernight' ), 30, 2 );
22
+ }
23
+
24
+ add_action( 'wpo_wcpdf_after_settings_page', array( $this, 'dompdf_status' ), 20, 2 );
25
+ }
26
+
27
+ public function output( $section ) {
28
+ settings_fields( "wpo_wcpdf_settings_debug" );
29
+ do_settings_sections( "wpo_wcpdf_settings_debug" );
30
+
31
+ submit_button();
32
+ }
33
+
34
+ public function debug_tools( $tab, $section ) {
35
+ if ($tab !== 'debug') {
36
+ return;
37
+ }
38
+ ?>
39
+ <form method="post">
40
+ <?php wp_nonce_field( 'wpo_wcpdf_debug_tools_action', 'security' ); ?>
41
+ <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="install_fonts">
42
+ <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Reinstall fonts', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
43
+ <?php
44
+ if ( !empty($_POST) && isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'install_fonts' ) {
45
+ // check permissions
46
+ if ( !check_admin_referer( 'wpo_wcpdf_debug_tools_action', 'security' ) ) {
47
+ return;
48
+ }
49
+
50
+ $font_path = WPO_WCPDF()->main->get_tmp_path( 'fonts' );
51
+
52
+ // clear folder first
53
+ if ( function_exists("glob") && $files = glob( $font_path.'/*.*' ) ) {
54
+ $exclude_files = array( 'index.php', '.htaccess' );
55
+ foreach($files as $file) {
56
+ if( is_file($file) && !in_array( basename($file), $exclude_files ) ) {
57
+ unlink($file);
58
+ }
59
+ }
60
+ }
61
+
62
+ WPO_WCPDF()->main->copy_fonts( $font_path );
63
+ printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Fonts reinstalled!', 'woocommerce-pdf-invoices-packing-slips' ) );
64
+ }
65
+ ?>
66
+ </form>
67
+ <form method="post">
68
+ <?php wp_nonce_field( 'wpo_wcpdf_debug_tools_action', 'security' ); ?>
69
+ <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="clear_tmp">
70
+ <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Remove temporary files', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
71
+ <?php
72
+ if ( !empty($_POST) && isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'clear_tmp' ) {
73
+ // check permissions
74
+ if ( !check_admin_referer( 'wpo_wcpdf_debug_tools_action', 'security' ) ) {
75
+ return;
76
+ }
77
+ $tmp_path = WPO_WCPDF()->main->get_tmp_path('attachments');
78
+
79
+ if ( !function_exists("glob") ) {
80
+ // glob is disabled
81
+ printf('<div class="notice notice-error"><p>%s<br><code>%s</code></p></div>', __( "Unable to read temporary folder contents!", 'woocommerce-pdf-invoices-packing-slips' ), $tmp_path);
82
+ } else {
83
+ $success = 0;
84
+ $error = 0;
85
+ if ( $files = glob($tmp_path.'*.pdf') ) { // get all pdf files
86
+ foreach($files as $file) {
87
+ if(is_file($file)) {
88
+ // delete file
89
+ if ( unlink($file) === true ) {
90
+ $success++;
91
+ } else {
92
+ $error++;
93
+ }
94
+ }
95
+ }
96
+
97
+ if ($error > 0) {
98
+ $message = sprintf( __( 'Unable to delete %d files! (deleted %d)', 'woocommerce-pdf-invoices-packing-slips' ), $error, $success);
99
+ printf('<div class="notice notice-error"><p>%s</p></div>', $message);
100
+ } else {
101
+ $message = sprintf( __( 'Successfully deleted %d files!', 'woocommerce-pdf-invoices-packing-slips' ), $success );
102
+ printf('<div class="notice notice-success"><p>%s</p></div>', $message);
103
+ }
104
+ } else {
105
+ printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Nothing to delete!', 'woocommerce-pdf-invoices-packing-slips' ) );
106
+ }
107
+ }
108
+ }
109
+ ?>
110
+ </form>
111
+ <form method="post">
112
+ <?php wp_nonce_field( 'wpo_wcpdf_debug_tools_action', 'security' ); ?>
113
+ <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="delete_legacy_settings">
114
+ <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Delete legacy (1.X) settings', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
115
+ <?php
116
+ if ( !empty($_POST) && isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'delete_legacy_settings' ) {
117
+ // check permissions
118
+ if ( !check_admin_referer( 'wpo_wcpdf_debug_tools_action', 'security' ) ) {
119
+ return;
120
+ }
121
+ // delete options
122
+ delete_option( 'wpo_wcpdf_general_settings' );
123
+ delete_option( 'wpo_wcpdf_template_settings' );
124
+ delete_option( 'wpo_wcpdf_debug_settings' );
125
+ // and delete cache of these options, just in case...
126
+ wp_cache_delete( 'wpo_wcpdf_general_settings','options' );
127
+ wp_cache_delete( 'wpo_wcpdf_template_settings','options' );
128
+ wp_cache_delete( 'wpo_wcpdf_debug_settings','options' );
129
+
130
+ printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Legacy settings deleted!', 'woocommerce-pdf-invoices-packing-slips' ) );
131
+ }
132
+ ?>
133
+ </form>
134
+ <?php
135
+ }
136
+
137
+ public function work_at_wpovernight( $tab, $section ) {
138
+ if ($tab === 'debug') {
139
+ include( WPO_WCPDF()->plugin_path() . '/includes/views/work-at-wpovernight.php' );
140
+ }
141
+ }
142
+
143
+ public function dompdf_status( $tab, $section ) {
144
+ if ($tab === 'debug') {
145
+ include( WPO_WCPDF()->plugin_path() . '/includes/views/dompdf-status.php' );
146
+ }
147
+ }
148
+
149
+ public function init_settings() {
150
+ // Register settings.
151
+ $page = $option_group = $option_name = 'wpo_wcpdf_settings_debug';
152
+
153
+ $settings_fields = array(
154
+ array(
155
+ 'type' => 'section',
156
+ 'id' => 'debug_settings',
157
+ 'title' => __( 'Debug settings', 'woocommerce-pdf-invoices-packing-slips' ),
158
+ 'callback' => 'section',
159
+ ),
160
+ array(
161
+ 'type' => 'setting',
162
+ 'id' => 'legacy_mode',
163
+ 'title' => __( 'Legacy mode', 'woocommerce-pdf-invoices-packing-slips' ),
164
+ 'callback' => 'checkbox',
165
+ 'section' => 'debug_settings',
166
+ 'args' => array(
167
+ 'option_name' => $option_name,
168
+ 'id' => 'legacy_mode',
169
+ 'description' => __( "Legacy mode ensures compatibility with templates and filters from previous versions.", 'woocommerce-pdf-invoices-packing-slips' ),
170
+ )
171
+ ),
172
+ array(
173
+ 'type' => 'setting',
174
+ 'id' => 'guest_access',
175
+ 'title' => __( 'Allow guest access', 'woocommerce-pdf-invoices-packing-slips' ),
176
+ 'callback' => 'checkbox',
177
+ 'section' => 'debug_settings',
178
+ 'args' => array(
179
+ 'option_name' => $option_name,
180
+ 'id' => 'guest_access',
181
+ 'description' => __( 'Enable this to allow customers that purchase without an account to access their PDF with a unique key', 'woocommerce-pdf-invoices-packing-slips' ),
182
+ )
183
+ ),
184
+ array(
185
+ 'type' => 'setting',
186
+ 'id' => 'calculate_document_numbers',
187
+ 'title' => __( 'Calculate document numbers (slow)', 'woocommerce-pdf-invoices-packing-slips' ),
188
+ 'callback' => 'checkbox',
189
+ 'section' => 'debug_settings',
190
+ 'args' => array(
191
+ 'option_name' => $option_name,
192
+ 'id' => 'calculate_document_numbers',
193
+ 'description' => __( "Document numbers (such as invoice numbers) are generated using AUTO_INCREMENT by default. Use this setting if your database auto increments with more than 1.", 'woocommerce-pdf-invoices-packing-slips' ),
194
+ )
195
+ ),
196
+ array(
197
+ 'type' => 'setting',
198
+ 'id' => 'enable_debug',
199
+ 'title' => __( 'Enable debug output', 'woocommerce-pdf-invoices-packing-slips' ),
200
+ 'callback' => 'checkbox',
201
+ 'section' => 'debug_settings',
202
+ 'args' => array(
203
+ 'option_name' => $option_name,
204
+ 'id' => 'enable_debug',
205
+ 'description' => __( "Enable this option to output plugin errors if you're getting a blank page or other PDF generation issues", 'woocommerce-pdf-invoices-packing-slips' ) . '<br>' .
206
+ __( '<b>Caution!</b> This setting may reveal errors (from other plugins) in other places on your site too, therefor this is not recommended to leave it enabled on live sites.', 'woocommerce-pdf-invoices-packing-slips' ) . ' ' .
207
+ __( 'You can also add <code>&debug=true</code> to the URL to apply this on a per-order basis.', 'woocommerce-pdf-invoices-packing-slips' ),
208
+ )
209
+ ),
210
+ array(
211
+ 'type' => 'setting',
212
+ 'id' => 'enable_cleanup',
213
+ 'title' => __( 'Enable automatic cleanup', 'woocommerce-pdf-invoices-packing-slips' ),
214
+ 'callback' => 'checkbox_text_input',
215
+ 'section' => 'debug_settings',
216
+ 'args' => array(
217
+ 'option_name' => $option_name,
218
+ 'id' => 'enable_cleanup',
219
+ 'disabled' => ( !function_exists("glob") || !function_exists('filemtime') ) ? 1 : NULL,
220
+ 'text_input_wrap' => __( "every %s days", 'woocommerce-pdf-invoices-packing-slips' ),
221
+ 'text_input_size' => 4,
222
+ 'text_input_id' => 'cleanup_days',
223
+ 'text_input_default'=> 7,
224
+ 'description' => ( function_exists("glob") && function_exists('filemtime') ) ?
225
+ __( "Automatically clean up PDF files stored in the temporary folder (used for email attachments)", 'woocommerce-pdf-invoices-packing-slips' ) :
226
+ __( '<b>Disabled:</b> The PHP functions glob and filemtime are required for automatic cleanup but not enabled on your server.', 'woocommerce-pdf-invoices-packing-slips' ),
227
+ )
228
+ ),
229
+ array(
230
+ 'type' => 'setting',
231
+ 'id' => 'html_output',
232
+ 'title' => __( 'Output to HTML', 'woocommerce-pdf-invoices-packing-slips' ),
233
+ 'callback' => 'checkbox',
234
+ 'section' => 'debug_settings',
235
+ 'args' => array(
236
+ 'option_name' => $option_name,
237
+ 'id' => 'html_output',
238
+ 'description' => __( 'Send the template output as HTML to the browser instead of creating a PDF.', 'woocommerce-pdf-invoices-packing-slips' ) . ' ' .
239
+ __( 'You can also add <code>&output=html</code> to the URL to apply this on a per-order basis.', 'woocommerce-pdf-invoices-packing-slips' ),
240
+ )
241
+ ),
242
+ array(
243
+ 'type' => 'setting',
244
+ 'id' => 'use_html5_parser',
245
+ 'title' => __( 'Use alternative HTML5 parser to parse HTML', 'woocommerce-pdf-invoices-packing-slips' ),
246
+ 'callback' => 'checkbox',
247
+ 'section' => 'debug_settings',
248
+ 'args' => array(
249
+ 'option_name' => $option_name,
250
+ 'id' => 'use_html5_parser',
251
+ )
252
+ ),
253
+ );
254
+
255
+ // allow plugins to alter settings fields
256
+ $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_debug', $settings_fields, $page, $option_group, $option_name );
257
+ WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
258
+ return;
259
+ }
260
+
261
+ }
262
+
263
+ endif; // class_exists
264
+
265
  return new Settings_Debug();
includes/documents/abstract-wcpdf-order-document.php CHANGED
@@ -320,7 +320,7 @@ abstract class Order_Document {
320
  $refund_id = $order->get_id();
321
  $parent_order = wc_get_order( $order->get_parent_id() );
322
  }
323
- $note = $refund_id ? sprintf( __( '%s (refund #%s) was regenerated.', 'wpo_wcpdf_pro' ), ucfirst( $this->get_title() ), $refund_id ) : sprintf( __( '%s was regenerated', 'wpo_wcpdf_pro' ), ucfirst( $this->get_title() ) );
324
  $parent_order ? $parent_order->add_order_note( $note ) : $order->add_order_note( $note );
325
 
326
  do_action( 'wpo_wcpdf_regenerate_document', $this );
320
  $refund_id = $order->get_id();
321
  $parent_order = wc_get_order( $order->get_parent_id() );
322
  }
323
+ $note = $refund_id ? sprintf( __( '%s (refund #%s) was regenerated.', 'woocommerce-pdf-invoices-packing-slips' ), ucfirst( $this->get_title() ), $refund_id ) : sprintf( __( '%s was regenerated', 'woocommerce-pdf-invoices-packing-slips' ), ucfirst( $this->get_title() ) );
324
  $parent_order ? $parent_order->add_order_note( $note ) : $order->add_order_note( $note );
325
 
326
  do_action( 'wpo_wcpdf_regenerate_document', $this );
includes/views/wcpdf-extensions.php CHANGED
@@ -1,132 +1,132 @@
1
- <?php defined( 'ABSPATH' ) or exit; ?>
2
- <script type="text/javascript">
3
- jQuery(document).ready(function() {
4
- jQuery('.extensions .more').hide();
5
-
6
- jQuery('.extensions > li').click(function() {
7
- jQuery(this).toggleClass('expanded');
8
- jQuery(this).find('.more').slideToggle();
9
- });
10
- });
11
- </script>
12
-
13
- <div class="wcpdf-extensions-ad">
14
- <?php $no_pro = !class_exists('WooCommerce_PDF_IPS_Pro') && !class_exists('WooCommerce_PDF_IPS_Dropbox') && !class_exists('WooCommerce_PDF_IPS_Templates'); ?>
15
- <img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/wpo-helper.png'; ?>" class="wpo-helper">
16
- <h3><?php _e( 'Check out these premium extensions!', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
17
- <i>(<?php _e( 'click items to read more', 'woocommerce-pdf-invoices-packing-slips' ); ?>)</i>
18
- <ul class="extensions">
19
- <?php if ( $no_pro ): ?>
20
- <!-- No Pro extensions: Ad for PDF bundle -->
21
- <li>
22
- <?php _e('Premium PDF Invoice bundle: Everything you need for a perfect invoicing system', 'woocommerce-pdf-invoices-packing-slips' )?>
23
- <div class="more" style="display:none;">
24
- <h4><?php _e( 'Supercharge WooCommerce PDF Invoices & Packing Slips with the all our premium extensions:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h4>
25
- <?php _e( 'Professional features:', 'woocommerce-pdf-invoices-packing-slips' ); ?>
26
- <ul>
27
- <li><?php _e( 'Email/print/download <b>PDF Credit Notes & Proforma invoices</b>', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
28
- <li><?php _e( 'Send out a separate <b>notification email</b> with (or without) PDF invoices/packing slips, for example to a drop-shipper or a supplier.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
29
- <li><?php _e( 'Attach <b>up to 3 static files</b> (for example a terms & conditions document) to the WooCommerce emails of your choice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
30
- <li><?php _e( 'Use <b>separate numbering systems</b> and/or format for proforma invoices and credit notes or utilize the main invoice numbering system', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
31
- <li><?php _e( '<b>Customize</b> the <b>shipping & billing address</b> format to include additional custom fields, font sizes etc. without the need to create a custom template.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
32
- <li><?php _e( 'Use the plugin in multilingual <b>WPML</b> setups', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
33
- </ul>
34
- <?php _e('Advanced, customizable templates', 'woocommerce-pdf-invoices-packing-slips' )?>
35
- <ul>
36
- <li><?php _e( 'Completely customize the invoice contents (prices, taxes, thumbnails) to your needs with a drag & drop customizer', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
37
- <li><?php _e( 'Two extra stylish premade templates (Modern & Business)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
38
- </ul>
39
- <?php _e('Upload automatically to dropbox', 'woocommerce-pdf-invoices-packing-slips' )?>
40
- <ul>
41
- <li><?php _e( 'This extension conveniently uploads all the invoices (and other pdf documents from the professional extension) that are emailed to your customers to Dropbox. The best way to keep your invoice administration up to date!', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
42
- </ul>
43
- <br>
44
- <a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-bundle/" target="_blank"><?php _e("Get WooCommerce PDF Invoices & Packing Slips Bundle", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
45
- </div>
46
- </li>
47
- <?php endif; ?>
48
- <?php
49
- // NO BUNDLE: separate ads
50
- if (!class_exists('WooCommerce_PDF_IPS_Pro') && !$no_pro) {
51
- ?>
52
- <li>
53
- <?php _e('Go Pro: Proforma invoices, credit notes (=refunds) & more!', 'woocommerce-pdf-invoices-packing-slips' )?>
54
- <div class="more" style="display:none;">
55
- <?php _e( 'Supercharge WooCommerce PDF Invoices & Packing Slips with the following features:', 'woocommerce-pdf-invoices-packing-slips' ); ?>
56
- <ul>
57
- <li><?php _e( 'Email/print/download <b>PDF Credit Notes & Proforma invoices</b>', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
58
- <li><?php _e( 'Send out a separate <b>notification email</b> with (or without) PDF invoices/packing slips, for example to a drop-shipper or a supplier.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
59
- <li><?php _e( 'Attach <b>up to 3 static files</b> (for example a terms & conditions document) to the WooCommerce emails of your choice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
60
- <li><?php _e( 'Use <b>separate numbering systems</b> and/or format for proforma invoices and credit notes or utilize the main invoice numbering system', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
61
- <li><?php _e( '<b>Customize</b> the <b>shipping & billing address</b> format to include additional custom fields, font sizes etc. without the need to create a custom template.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
62
- <li><?php _e( 'Use the plugin in multilingual <b>WPML</b> setups', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
63
- <li><?php _e( 'Upload automatically to dropbox', 'woocommerce-pdf-invoices-packing-slips' )?></li>
64
- </ul>
65
- <a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/" target="_blank"><?php _e("Get WooCommerce PDF Invoices & Packing Slips Professional!", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
66
- </li>
67
- <?php } ?>
68
-
69
- <?php
70
- if (!class_exists('WPO_WC_Smart_Reminder_Emails')) {
71
- ?>
72
- <li>
73
- <?php _e('Automatically send payment reminders to your customers', 'woocommerce-pdf-invoices-packing-slips' )?>
74
- <div class="more" style="display:none;">
75
- <?php _e('WooCommerce Smart Reminder emails', 'woocommerce-pdf-invoices-packing-slips' )?>
76
- <ul>
77
- <li><?php _e( '<b>Completely automatic</b> scheduled emails', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
78
- <li><?php _e( '<b>Rich text editor</b> for the email text, including placeholders for data from the order (name, order total, etc)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
79
- <li><?php _e( 'Configure the exact requirements for sending an email (time after order, order status, payment method)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
80
- <li><?php _e( 'Fully <b>WPML Compatible</b> – emails will be automatically sent in the order language.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
81
- <li><?php _e( '<b>Super versatile!</b> Can be used for any kind of reminder email (review reminders, repeat purchases)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
82
- <li><b><?php _e( 'Integrates seamlessly with the PDF Invoices & Packing Slips plugin', 'woocommerce-pdf-invoices-packing-slips' ); ?></b></li>
83
- </ul>
84
- <a href="https://wpovernight.com/downloads/woocommerce-reminder-emails-payment-reminders/" target="_blank"><?php _e("Get WooCommerce Smart Reminder Emails", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
85
- </div>
86
- </li>
87
- <?php } ?>
88
-
89
- <?php
90
- if (!class_exists('WooCommerce_Ext_PrintOrders')) {
91
- ?>
92
- <li>
93
- <?php _e('Automatically send new orders or packing slips to your printer, as soon as the customer orders!', 'woocommerce-pdf-invoices-packing-slips' )?>
94
- <div class="more" style="display:none;">
95
- <table>
96
- <tr>
97
- <td><img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/cloud-print.png'; ?>" class="cloud-logo"></td>
98
- <td>
99
- <?php _e( 'Check out the WooCommerce Automatic Order Printing extension from our partners at Simba Hosting', 'woocommerce-pdf-invoices-packing-slips' ); ?><br/>
100
- <a href="https://www.simbahosting.co.uk/s3/product/woocommerce-automatic-order-printing/?affiliates=2" target="_blank"><?php _e("WooCommerce Automatic Order Printing", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
101
- </td>
102
- </tr>
103
- </table>
104
- </div>
105
- </li>
106
- <?php } ?>
107
-
108
- <?php
109
- if (!class_exists('WooCommerce_PDF_IPS_Templates') && !class_exists('WPO_WCPDF_Templates') && !$no_pro) {
110
- $template_link = '<a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/" target="_blank">wpovernight.com</a>';
111
- $email_link = '<a href="mailto:support@wpovernight.com">support@wpovernight.com</a>'
112
- ?>
113
- <li>
114
- <?php _e('Advanced, customizable templates', 'woocommerce-pdf-invoices-packing-slips' )?>
115
- <div class="more" style="display:none;">
116
- <ul>
117
- <li><?php _e( 'Completely customize the invoice contents (prices, taxes, thumbnails) to your needs with a drag & drop customizer', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
118
- <li><?php _e( 'Two extra stylish premade templates (Modern & Business)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
119
- <li><?php printf( __("Check out the Premium PDF Invoice & Packing Slips templates at %s.", 'woocommerce-pdf-invoices-packing-slips' ), $template_link );?></li>
120
- <li><?php printf( __("For custom templates, contact us at %s.", 'woocommerce-pdf-invoices-packing-slips' ), $email_link );?></li>
121
- </ul>
122
- </div>
123
- </li>
124
- <?php } ?>
125
- </ul>
126
- <?php
127
- // link to hide message when one of the premium extensions is installed
128
- if ( class_exists('WooCommerce_PDF_IPS_Pro') || class_exists('WooCommerce_PDF_IPS_Dropbox') || class_exists('WPO_WCPDF_Templates') || class_exists('WooCommerce_PDF_IPS_Templates') || class_exists('WooCommerce_Ext_PrintOrders') || class_exists('WPO_WC_Smart_Reminder_Emails') ) {
129
- printf('<a href="%s" style="display:inline-block; margin-top: 10px;">%s</a>', add_query_arg( 'wpo_wcpdf_hide_extensions_ad', 'true' ), __( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ) );
130
- }
131
- ?>
132
  </div>
1
+ <?php defined( 'ABSPATH' ) or exit; ?>
2
+ <script type="text/javascript">
3
+ jQuery(document).ready(function() {
4
+ jQuery('.extensions .more').hide();
5
+
6
+ jQuery('.extensions > li').click(function() {
7
+ jQuery(this).toggleClass('expanded');
8
+ jQuery(this).find('.more').slideToggle();
9
+ });
10
+ });
11
+ </script>
12
+
13
+ <div class="wcpdf-extensions-ad">
14
+ <?php $no_pro = !class_exists('WooCommerce_PDF_IPS_Pro') && !class_exists('WooCommerce_PDF_IPS_Dropbox') && !class_exists('WPO_WCPDF_Templates'); ?>
15
+ <img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/wpo-helper.png'; ?>" class="wpo-helper">
16
+ <h3><?php _e( 'Check out these premium extensions!', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
17
+ <i>(<?php _e( 'click items to read more', 'woocommerce-pdf-invoices-packing-slips' ); ?>)</i>
18
+ <ul class="extensions">
19
+ <?php if ( $no_pro ): ?>
20
+ <!-- No Pro extensions: Ad for PDF bundle -->
21
+ <li>
22
+ <?php _e('Premium PDF Invoice bundle: Everything you need for a perfect invoicing system', 'woocommerce-pdf-invoices-packing-slips' )?>
23
+ <div class="more" style="display:none;">
24
+ <h4><?php _e( 'Supercharge WooCommerce PDF Invoices & Packing Slips with the all our premium extensions:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h4>
25
+ <?php _e( 'Professional features:', 'woocommerce-pdf-invoices-packing-slips' ); ?>
26
+ <ul>
27
+ <li><?php _e( 'Email/print/download <b>PDF Credit Notes & Proforma invoices</b>', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
28
+ <li><?php _e( 'Send out a separate <b>notification email</b> with (or without) PDF invoices/packing slips, for example to a drop-shipper or a supplier.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
29
+ <li><?php _e( 'Attach <b>up to 3 static files</b> (for example a terms & conditions document) to the WooCommerce emails of your choice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
30
+ <li><?php _e( 'Use <b>separate numbering systems</b> and/or format for proforma invoices and credit notes or utilize the main invoice numbering system', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
31
+ <li><?php _e( '<b>Customize</b> the <b>shipping & billing address</b> format to include additional custom fields, font sizes etc. without the need to create a custom template.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
32
+ <li><?php _e( 'Use the plugin in multilingual <b>WPML</b> setups', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
33
+ </ul>
34
+ <?php _e('Advanced, customizable templates', 'woocommerce-pdf-invoices-packing-slips' )?>
35
+ <ul>
36
+ <li><?php _e( 'Completely customize the invoice contents (prices, taxes, thumbnails) to your needs with a drag & drop customizer', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
37
+ <li><?php _e( 'Two extra stylish premade templates (Modern & Business)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
38
+ </ul>
39
+ <?php _e('Upload automatically to dropbox', 'woocommerce-pdf-invoices-packing-slips' )?>
40
+ <ul>
41
+ <li><?php _e( 'This extension conveniently uploads all the invoices (and other pdf documents from the professional extension) that are emailed to your customers to Dropbox. The best way to keep your invoice administration up to date!', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
42
+ </ul>
43
+ <br>
44
+ <a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-bundle/" target="_blank"><?php _e("Get WooCommerce PDF Invoices & Packing Slips Bundle", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
45
+ </div>
46
+ </li>
47
+ <?php endif; ?>
48
+ <?php
49
+ // NO BUNDLE: separate ads
50
+ if (!class_exists('WooCommerce_PDF_IPS_Pro') && !$no_pro) {
51
+ ?>
52
+ <li>
53
+ <?php _e('Go Pro: Proforma invoices, credit notes (=refunds) & more!', 'woocommerce-pdf-invoices-packing-slips' )?>
54
+ <div class="more" style="display:none;">
55
+ <?php _e( 'Supercharge WooCommerce PDF Invoices & Packing Slips with the following features:', 'woocommerce-pdf-invoices-packing-slips' ); ?>
56
+ <ul>
57
+ <li><?php _e( 'Email/print/download <b>PDF Credit Notes & Proforma invoices</b>', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
58
+ <li><?php _e( 'Send out a separate <b>notification email</b> with (or without) PDF invoices/packing slips, for example to a drop-shipper or a supplier.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
59
+ <li><?php _e( 'Attach <b>up to 3 static files</b> (for example a terms & conditions document) to the WooCommerce emails of your choice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
60
+ <li><?php _e( 'Use <b>separate numbering systems</b> and/or format for proforma invoices and credit notes or utilize the main invoice numbering system', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
61
+ <li><?php _e( '<b>Customize</b> the <b>shipping & billing address</b> format to include additional custom fields, font sizes etc. without the need to create a custom template.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
62
+ <li><?php _e( 'Use the plugin in multilingual <b>WPML</b> setups', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
63
+ <li><?php _e( 'Upload automatically to dropbox', 'woocommerce-pdf-invoices-packing-slips' )?></li>
64
+ </ul>
65
+ <a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/" target="_blank"><?php _e("Get WooCommerce PDF Invoices & Packing Slips Professional!", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
66
+ </li>
67
+ <?php } ?>
68
+
69
+ <?php
70
+ if (!class_exists('WPO_WC_Smart_Reminder_Emails')) {
71
+ ?>
72
+ <li>
73
+ <?php _e('Automatically send payment reminders to your customers', 'woocommerce-pdf-invoices-packing-slips' )?>
74
+ <div class="more" style="display:none;">
75
+ <?php _e('WooCommerce Smart Reminder emails', 'woocommerce-pdf-invoices-packing-slips' )?>
76
+ <ul>
77
+ <li><?php _e( '<b>Completely automatic</b> scheduled emails', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
78
+ <li><?php _e( '<b>Rich text editor</b> for the email text, including placeholders for data from the order (name, order total, etc)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
79
+ <li><?php _e( 'Configure the exact requirements for sending an email (time after order, order status, payment method)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
80
+ <li><?php _e( 'Fully <b>WPML Compatible</b> – emails will be automatically sent in the order language.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
81
+ <li><?php _e( '<b>Super versatile!</b> Can be used for any kind of reminder email (review reminders, repeat purchases)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
82
+ <li><b><?php _e( 'Integrates seamlessly with the PDF Invoices & Packing Slips plugin', 'woocommerce-pdf-invoices-packing-slips' ); ?></b></li>
83
+ </ul>
84
+ <a href="https://wpovernight.com/downloads/woocommerce-reminder-emails-payment-reminders/" target="_blank"><?php _e("Get WooCommerce Smart Reminder Emails", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
85
+ </div>
86
+ </li>
87
+ <?php } ?>
88
+
89
+ <?php
90
+ if (!class_exists('WooCommerce_Ext_PrintOrders')) {
91
+ ?>
92
+ <li>
93
+ <?php _e('Automatically send new orders or packing slips to your printer, as soon as the customer orders!', 'woocommerce-pdf-invoices-packing-slips' )?>
94
+ <div class="more" style="display:none;">
95
+ <table>
96
+ <tr>
97
+ <td><img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/cloud-print.png'; ?>" class="cloud-logo"></td>
98
+ <td>
99
+ <?php _e( 'Check out the WooCommerce Automatic Order Printing extension from our partners at Simba Hosting', 'woocommerce-pdf-invoices-packing-slips' ); ?><br/>
100
+ <a href="https://www.simbahosting.co.uk/s3/product/woocommerce-printnode-automatic-order-printing/?affiliates=2" target="_blank"><?php _e("WooCommerce Automatic Order Printing", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
101
+ </td>
102
+ </tr>
103
+ </table>
104
+ </div>
105
+ </li>
106
+ <?php } ?>
107
+
108
+ <?php
109
+ if (!class_exists('WooCommerce_PDF_IPS_Templates') && !class_exists('WPO_WCPDF_Templates') && !$no_pro) {
110
+ $template_link = '<a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/" target="_blank">wpovernight.com</a>';
111
+ $email_link = '<a href="mailto:support@wpovernight.com">support@wpovernight.com</a>'
112
+ ?>
113
+ <li>
114
+ <?php _e('Advanced, customizable templates', 'woocommerce-pdf-invoices-packing-slips' )?>
115
+ <div class="more" style="display:none;">
116
+ <ul>
117
+ <li><?php _e( 'Completely customize the invoice contents (prices, taxes, thumbnails) to your needs with a drag & drop customizer', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
118
+ <li><?php _e( 'Two extra stylish premade templates (Modern & Business)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
119
+ <li><?php printf( __("Check out the Premium PDF Invoice & Packing Slips templates at %s.", 'woocommerce-pdf-invoices-packing-slips' ), $template_link );?></li>
120
+ <li><?php printf( __("For custom templates, contact us at %s.", 'woocommerce-pdf-invoices-packing-slips' ), $email_link );?></li>
121
+ </ul>
122
+ </div>
123
+ </li>
124
+ <?php } ?>
125
+ </ul>
126
+ <?php
127
+ // link to hide message when one of the premium extensions is installed
128
+ if ( class_exists('WooCommerce_PDF_IPS_Pro') || class_exists('WooCommerce_PDF_IPS_Dropbox') || class_exists('WPO_WCPDF_Templates') || class_exists('WooCommerce_PDF_IPS_Templates') || class_exists('WooCommerce_Ext_PrintOrders') || class_exists('WPO_WC_Smart_Reminder_Emails') ) {
129
+ printf('<a href="%s" style="display:inline-block; margin-top: 10px;">%s</a>', add_query_arg( 'wpo_wcpdf_hide_extensions_ad', 'true' ), __( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ) );
130
+ }
131
+ ?>
132
  </div>
languages/woocommerce-pdf-invoices-packing-slips.pot CHANGED
@@ -1,1057 +1,1202 @@
1
- #, fuzzy
2
- msgid ""
3
- msgstr ""
4
- "Project-Id-Version: WooCommerce PDF Invoices & Packing Slips\n"
5
- "POT-Creation-Date: 2018-10-10 10:05+0200\n"
6
- "PO-Revision-Date: 2015-04-29 08:58+0100\n"
7
- "Last-Translator: \n"
8
- "Language-Team: WP Overnight <support@wpovernight.com>\n"
9
- "Language: en_US\n"
10
- "MIME-Version: 1.0\n"
11
- "Content-Type: text/plain; charset=UTF-8\n"
12
- "Content-Transfer-Encoding: 8bit\n"
13
- "X-Generator: Poedit 1.8.12\n"
14
- "X-Poedit-Basepath: ..\n"
15
- "Plural-Forms: nplurals=2; plural=n != 1;\n"
16
- "X-Poedit-SourceCharset: UTF-8\n"
17
- "X-Poedit-KeywordsList: __;_e;_x;_n:1,2\n"
18
- "X-Poedit-SearchPath-0: .\n"
19
-
20
- #: includes/class-wcpdf-admin.php:79
21
- #, php-format
22
- msgid "Wow, you have created more than %d invoices with our plugin!"
23
- msgstr ""
24
-
25
- #: includes/class-wcpdf-admin.php:80
26
- msgid ""
27
- "It would mean a lot to us if you would quickly give our plugin a 5-star "
28
- "rating. Help us spread the word and boost our motivation!"
29
- msgstr ""
30
-
31
- #: includes/class-wcpdf-admin.php:82
32
- msgid "Yes you deserve it!"
33
- msgstr ""
34
-
35
- #: includes/class-wcpdf-admin.php:83
36
- #: includes/views/attachment-settings-hint.php:22
37
- #: includes/views/wcpdf-extensions.php:128
38
- msgid "Hide this message"
39
- msgstr ""
40
-
41
- #: includes/class-wcpdf-admin.php:83
42
- msgid "Already did!"
43
- msgstr ""
44
-
45
- #: includes/class-wcpdf-admin.php:84
46
- msgid "Actually, I have a complaint..."
47
- msgstr ""
48
-
49
- #: includes/class-wcpdf-admin.php:119
50
- msgid "New to WooCommerce PDF Invoices & Packing Slips?"
51
- msgstr ""
52
-
53
- #: includes/class-wcpdf-admin.php:119
54
- msgid "Jumpstart the plugin by following our wizard!"
55
- msgstr ""
56
-
57
- #: includes/class-wcpdf-admin.php:120
58
- msgid "Run the Setup Wizard"
59
- msgstr ""
60
-
61
- #: includes/class-wcpdf-admin.php:120
62
- msgid "I am the wizard"
63
- msgstr ""
64
-
65
- #: includes/class-wcpdf-admin.php:201 includes/class-wcpdf-admin.php:351
66
- #: includes/class-wcpdf-main.php:567
67
- msgid "Invoice Number"
68
- msgstr ""
69
-
70
- #: includes/class-wcpdf-admin.php:237
71
- msgid "Send order email"
72
- msgstr ""
73
-
74
- #: includes/class-wcpdf-admin.php:248
75
- msgid "Create PDF"
76
- msgstr ""
77
-
78
- #: includes/class-wcpdf-admin.php:258
79
- msgid "PDF Invoice data"
80
- msgstr ""
81
-
82
- #: includes/class-wcpdf-admin.php:292
83
- msgid "Send email"
84
- msgstr ""
85
-
86
- #: includes/class-wcpdf-admin.php:360 includes/class-wcpdf-admin.php:381
87
- #: templates/Simple/invoice.php:60
88
- msgid "Invoice Date:"
89
- msgstr ""
90
-
91
- #: includes/class-wcpdf-admin.php:366
92
- msgid "Set invoice number & date"
93
- msgstr ""
94
-
95
- #: includes/class-wcpdf-admin.php:373
96
- msgid "Invoice Number (unformatted!)"
97
- msgstr ""
98
-
99
- #: includes/class-wcpdf-admin.php:383 includes/class-wcpdf-admin.php:385
100
- msgid "h"
101
- msgstr ""
102
-
103
- #: includes/class-wcpdf-admin.php:383 includes/class-wcpdf-admin.php:385
104
- msgid "m"
105
- msgstr ""
106
-
107
- #. translators: %s: email title
108
- #: includes/class-wcpdf-admin.php:476
109
- #, php-format
110
- msgid "%s email notification manually sent."
111
- msgstr ""
112
-
113
- #: includes/class-wcpdf-assets.php:79
114
- msgid "Are you sure you want to delete this document? This cannot be undone."
115
- msgstr ""
116
-
117
- #: includes/class-wcpdf-frontend.php:55
118
- #, php-format
119
- msgid "Download %s (PDF)"
120
- msgstr ""
121
-
122
- #: includes/class-wcpdf-frontend.php:57
123
- msgid "Download invoice (PDF)"
124
- msgstr ""
125
-
126
- #: includes/class-wcpdf-main.php:162 includes/class-wcpdf-main.php:214
127
- msgid "You do not have sufficient permissions to access this page."
128
- msgstr ""
129
-
130
- #: includes/class-wcpdf-main.php:171
131
- msgid "You haven't selected any orders"
132
- msgstr ""
133
-
134
- #: includes/class-wcpdf-main.php:175
135
- msgid "Some of the export parameters are missing."
136
- msgstr ""
137
-
138
- #: includes/class-wcpdf-main.php:237
139
- #, php-format
140
- msgid "Document of type '%s' for the selected order(s) could not be generated"
141
- msgstr ""
142
-
143
- #: includes/class-wcpdf-main.php:568
144
- msgid "Invoice Date"
145
- msgstr ""
146
-
147
- #: includes/class-wcpdf-settings-callbacks.php:27
148
- msgid ""
149
- "<b>Warning!</b> The settings below are meant for debugging/development only. "
150
- "Do not use them on a live website!"
151
- msgstr ""
152
-
153
- #: includes/class-wcpdf-settings-callbacks.php:36
154
- msgid ""
155
- "These are used for the (optional) footer columns in the <em>Modern "
156
- "(Premium)</em> template, but can also be used for other elements in your "
157
- "custom template"
158
- msgstr ""
159
-
160
- #: includes/class-wcpdf-settings-callbacks.php:347
161
- msgid "Image resolution"
162
- msgstr ""
163
-
164
- #: includes/class-wcpdf-settings-callbacks.php:372
165
- msgid "Save"
166
- msgstr ""
167
-
168
- #: includes/class-wcpdf-settings-debug.php:41
169
- msgid "Reinstall fonts"
170
- msgstr ""
171
-
172
- #: includes/class-wcpdf-settings-debug.php:57
173
- msgid "Fonts reinstalled!"
174
- msgstr ""
175
-
176
- #: includes/class-wcpdf-settings-debug.php:63
177
- msgid "Remove temporary files"
178
- msgstr ""
179
-
180
- #: includes/class-wcpdf-settings-debug.php:70
181
- msgid "Unable to read temporary folder contents!"
182
- msgstr ""
183
-
184
- #: includes/class-wcpdf-settings-debug.php:87
185
- #, php-format
186
- msgid "Unable to delete %d files! (deleted %d)"
187
- msgstr ""
188
-
189
- #: includes/class-wcpdf-settings-debug.php:90
190
- #, php-format
191
- msgid "Successfully deleted %d files!"
192
- msgstr ""
193
-
194
- #: includes/class-wcpdf-settings-debug.php:94
195
- msgid "Nothing to delete!"
196
- msgstr ""
197
-
198
- #: includes/class-wcpdf-settings-debug.php:102
199
- msgid "Delete legacy (1.X) settings"
200
- msgstr ""
201
-
202
- #: includes/class-wcpdf-settings-debug.php:114
203
- msgid "Legacy settings deleted!"
204
- msgstr ""
205
-
206
- #: includes/class-wcpdf-settings-debug.php:141
207
- msgid "Debug settings"
208
- msgstr ""
209
-
210
- #: includes/class-wcpdf-settings-debug.php:147
211
- msgid "Legacy mode"
212
- msgstr ""
213
-
214
- #: includes/class-wcpdf-settings-debug.php:153
215
- msgid ""
216
- "Legacy mode ensures compatibility with templates and filters from previous "
217
- "versions."
218
- msgstr ""
219
-
220
- #: includes/class-wcpdf-settings-debug.php:159
221
- msgid "Calculate document numbers (slow)"
222
- msgstr ""
223
-
224
- #: includes/class-wcpdf-settings-debug.php:165
225
- msgid ""
226
- "Document numbers (such as invoice numbers) are generated using "
227
- "AUTO_INCREMENT by default. Use this setting if your database auto increments "
228
- "with more than 1."
229
- msgstr ""
230
-
231
- #: includes/class-wcpdf-settings-debug.php:171
232
- msgid "Enable debug output"
233
- msgstr ""
234
-
235
- #: includes/class-wcpdf-settings-debug.php:177
236
- msgid ""
237
- "Enable this option to output plugin errors if you're getting a blank page or "
238
- "other PDF generation issues"
239
- msgstr ""
240
-
241
- #: includes/class-wcpdf-settings-debug.php:178
242
- msgid ""
243
- "<b>Caution!</b> This setting may reveal errors (from other plugins) in other "
244
- "places on your site too, therefor this is not recommended to leave it "
245
- "enabled on live sites."
246
- msgstr ""
247
-
248
- #: includes/class-wcpdf-settings-debug.php:184
249
- msgid "Enable automatic cleanup"
250
- msgstr ""
251
-
252
- #: includes/class-wcpdf-settings-debug.php:191
253
- #, php-format
254
- msgid "every %s days"
255
- msgstr ""
256
-
257
- #: includes/class-wcpdf-settings-debug.php:196
258
- msgid ""
259
- "Automatically clean up PDF files stored in the temporary folder (used for "
260
- "email attachments)"
261
- msgstr ""
262
-
263
- #: includes/class-wcpdf-settings-debug.php:197
264
- msgid ""
265
- "<b>Disabled:</b> The PHP functions glob and filemtime are required for "
266
- "automatic cleanup but not enabled on your server."
267
- msgstr ""
268
-
269
- #: includes/class-wcpdf-settings-debug.php:203
270
- msgid "Output to HTML"
271
- msgstr ""
272
-
273
- #: includes/class-wcpdf-settings-debug.php:209
274
- msgid ""
275
- "Send the template output as HTML to the browser instead of creating a PDF."
276
- msgstr ""
277
-
278
- #: includes/class-wcpdf-settings-debug.php:215
279
- msgid "Use alternative HTML5 parser to parse HTML"
280
- msgstr ""
281
-
282
- #: includes/class-wcpdf-settings-documents.php:30
283
- #: includes/class-wcpdf-settings.php:96
284
- msgid "Documents"
285
- msgstr ""
286
-
287
- #: includes/class-wcpdf-settings-documents.php:46
288
- msgid ""
289
- "All available documents are listed below. Click on a document to configure "
290
- "it."
291
- msgstr ""
292
-
293
- #: includes/class-wcpdf-settings-general.php:38
294
- msgid "General settings"
295
- msgstr ""
296
-
297
- #: includes/class-wcpdf-settings-general.php:44
298
- msgid "How do you want to view the PDF?"
299
- msgstr ""
300
-
301
- #: includes/class-wcpdf-settings-general.php:51
302
- msgid "Download the PDF"
303
- msgstr ""
304
-
305
- #: includes/class-wcpdf-settings-general.php:52
306
- msgid "Open the PDF in a new browser tab/window"
307
- msgstr ""
308
-
309
- #: includes/class-wcpdf-settings-general.php:59
310
- msgid "Choose a template"
311
- msgstr ""
312
-
313
- #: includes/class-wcpdf-settings-general.php:66
314
- #, php-format
315
- msgid ""
316
- "Want to use your own template? Copy all the files from <code>%s</code> to "
317
- "your (child) theme in <code>%s</code> to customize them"
318
- msgstr ""
319
-
320
- #: includes/class-wcpdf-settings-general.php:72
321
- msgid "Paper size"
322
- msgstr ""
323
-
324
- #: includes/class-wcpdf-settings-general.php:79
325
- #: includes/views/setup-wizard/paper-format.php:11
326
- msgid "A4"
327
- msgstr ""
328
-
329
- #: includes/class-wcpdf-settings-general.php:80
330
- #: includes/views/setup-wizard/paper-format.php:12
331
- msgid "Letter"
332
- msgstr ""
333
-
334
- #: includes/class-wcpdf-settings-general.php:87
335
- msgid "Extended currency symbol support"
336
- msgstr ""
337
-
338
- #: includes/class-wcpdf-settings-general.php:93
339
- msgid "Enable this if your currency symbol is not displaying properly"
340
- msgstr ""
341
-
342
- #: includes/class-wcpdf-settings-general.php:99
343
- msgid "Enable font subsetting"
344
- msgstr ""
345
-
346
- #: includes/class-wcpdf-settings-general.php:105
347
- msgid ""
348
- "Font subsetting can reduce file size by only including the characters that "
349
- "are used in the PDF, but limits the ability to edit PDF files later. "
350
- "Recommended if you're using an Asian font."
351
- msgstr ""
352
-
353
- #: includes/class-wcpdf-settings-general.php:111
354
- msgid "Shop header/logo"
355
- msgstr ""
356
-
357
- #: includes/class-wcpdf-settings-general.php:117
358
- #: includes/views/setup-wizard/logo.php:19
359
- msgid "Select or upload your invoice header/logo"
360
- msgstr ""
361
-
362
- #: includes/class-wcpdf-settings-general.php:118
363
- #: includes/views/setup-wizard/logo.php:19
364
- msgid "Set image"
365
- msgstr ""
366
-
367
- #: includes/class-wcpdf-settings-general.php:119
368
- #: includes/views/setup-wizard/logo.php:19
369
- msgid "Remove image"
370
- msgstr ""
371
-
372
- #: includes/class-wcpdf-settings-general.php:126
373
- #: includes/class-wcpdf-setup-wizard.php:42
374
- msgid "Shop Name"
375
- msgstr ""
376
-
377
- #: includes/class-wcpdf-settings-general.php:139
378
- msgid "Shop Address"
379
- msgstr ""
380
-
381
- #: includes/class-wcpdf-settings-general.php:154
382
- msgid "Footer: terms & conditions, policies, etc."
383
- msgstr ""
384
-
385
- #: includes/class-wcpdf-settings-general.php:169
386
- msgid "Extra template fields"
387
- msgstr ""
388
-
389
- #: includes/class-wcpdf-settings-general.php:175
390
- msgid "Extra field 1"
391
- msgstr ""
392
-
393
- #: includes/class-wcpdf-settings-general.php:183
394
- msgid "This is footer column 1 in the <i>Modern (Premium)</i> template"
395
- msgstr ""
396
-
397
- #: includes/class-wcpdf-settings-general.php:190
398
- msgid "Extra field 2"
399
- msgstr ""
400
-
401
- #: includes/class-wcpdf-settings-general.php:198
402
- msgid "This is footer column 2 in the <i>Modern (Premium)</i> template"
403
- msgstr ""
404
-
405
- #: includes/class-wcpdf-settings-general.php:205
406
- msgid "Extra field 3"
407
- msgstr ""
408
-
409
- #: includes/class-wcpdf-settings-general.php:213
410
- msgid "This is footer column 3 in the <i>Modern (Premium)</i> template"
411
- msgstr ""
412
-
413
- #: includes/class-wcpdf-settings.php:48 includes/class-wcpdf-settings.php:49
414
- msgid "PDF Invoices"
415
- msgstr ""
416
-
417
- #: includes/class-wcpdf-settings.php:61
418
- msgid "Settings"
419
- msgstr ""
420
-
421
- #: includes/class-wcpdf-settings.php:74
422
- msgid "Documentation"
423
- msgstr ""
424
-
425
- #: includes/class-wcpdf-settings.php:75
426
- msgid "Support Forum"
427
- msgstr ""
428
-
429
- #: includes/class-wcpdf-settings.php:87
430
- #, php-format
431
- msgid ""
432
- "<strong>Warning!</strong> Your database has an AUTO_INCREMENT step size of "
433
- "%s, your invoice numbers may not be sequential. Enable the 'Calculate "
434
- "document numbers (slow)' setting in the Status tab to use an alternate "
435
- "method."
436
- msgstr ""
437
-
438
- #: includes/class-wcpdf-settings.php:95
439
- msgid "General"
440
- msgstr ""
441
-
442
- #: includes/class-wcpdf-settings.php:101
443
- msgid "Status"
444
- msgstr ""
445
-
446
- #: includes/class-wcpdf-setup-wizard.php:46
447
- #: includes/views/setup-wizard/logo.php:2
448
- msgid "Your logo"
449
- msgstr ""
450
-
451
- #: includes/class-wcpdf-setup-wizard.php:50
452
- msgid "Attachments"
453
- msgstr ""
454
-
455
- #: includes/class-wcpdf-setup-wizard.php:54
456
- #: includes/views/setup-wizard/display-options.php:2
457
- msgid "Display options"
458
- msgstr ""
459
-
460
- #: includes/class-wcpdf-setup-wizard.php:58
461
- #: includes/views/setup-wizard/paper-format.php:2
462
- msgid "Paper format"
463
- msgstr ""
464
-
465
- #: includes/class-wcpdf-setup-wizard.php:62
466
- msgid "Ready!"
467
- msgstr ""
468
-
469
- #: includes/class-wcpdf-setup-wizard.php:175
470
- msgid "Previous"
471
- msgstr ""
472
-
473
- #: includes/class-wcpdf-setup-wizard.php:181
474
- msgid "Skip this step"
475
- msgstr ""
476
-
477
- #: includes/class-wcpdf-setup-wizard.php:183
478
- msgid "Finish"
479
- msgstr ""
480
-
481
- #: includes/compatibility/class-wc-core-compatibility.php:222
482
- msgid "WooCommerce"
483
- msgstr ""
484
-
485
- #: includes/documents/abstract-wcpdf-order-document-methods.php:113
486
- #: includes/documents/abstract-wcpdf-order-document-methods.php:176
487
- msgid "N/A"
488
- msgstr ""
489
-
490
- #: includes/documents/abstract-wcpdf-order-document-methods.php:390
491
- msgid "Payment method"
492
- msgstr ""
493
-
494
- #: includes/documents/abstract-wcpdf-order-document-methods.php:411
495
- msgid "Shipping method"
496
- msgstr ""
497
-
498
- #: includes/documents/abstract-wcpdf-order-document-methods.php:788
499
- #, php-format
500
- msgid "(includes %s)"
501
- msgstr ""
502
-
503
- #: includes/documents/abstract-wcpdf-order-document-methods.php:791
504
- #, php-format
505
- msgid "(Includes %s)"
506
- msgstr ""
507
-
508
- #: includes/documents/abstract-wcpdf-order-document-methods.php:821
509
- msgid "Subtotal"
510
- msgstr ""
511
-
512
- #: includes/documents/abstract-wcpdf-order-document-methods.php:846
513
- msgid "Shipping"
514
- msgstr ""
515
-
516
- #: includes/documents/abstract-wcpdf-order-document-methods.php:909
517
- msgid "Discount"
518
- msgstr ""
519
-
520
- #: includes/documents/abstract-wcpdf-order-document-methods.php:950
521
- msgid "VAT"
522
- msgstr ""
523
-
524
- #: includes/documents/abstract-wcpdf-order-document-methods.php:951
525
- msgid "Tax rate"
526
- msgstr ""
527
-
528
- #: includes/documents/abstract-wcpdf-order-document-methods.php:995
529
- msgid "Total ex. VAT"
530
- msgstr ""
531
-
532
- #: includes/documents/abstract-wcpdf-order-document-methods.php:998
533
- msgid "Total"
534
- msgstr ""
535
-
536
- #: includes/documents/abstract-wcpdf-order-document.php:695
537
- msgid "Admin email"
538
- msgstr ""
539
-
540
- #: includes/documents/abstract-wcpdf-order-document.php:698
541
- msgid "Manual email"
542
- msgstr ""
543
-
544
- #: includes/documents/class-wcpdf-invoice.php:32
545
- #: includes/documents/class-wcpdf-invoice.php:41
546
- #: includes/legacy/class-wcpdf-legacy-functions.php:22
547
- msgid "Invoice"
548
- msgstr ""
549
-
550
- #: includes/documents/class-wcpdf-invoice.php:99
551
- msgid "invoice"
552
- msgid_plural "invoices"
553
- msgstr[0] ""
554
- msgstr[1] ""
555
-
556
- #: includes/documents/class-wcpdf-invoice.php:144
557
- #: includes/documents/class-wcpdf-packing-slip.php:88
558
- msgid "Enable"
559
- msgstr ""
560
-
561
- #: includes/documents/class-wcpdf-invoice.php:155
562
- msgid "Attach to:"
563
- msgstr ""
564
-
565
- #: includes/documents/class-wcpdf-invoice.php:162
566
- #, php-format
567
- msgid ""
568
- "It looks like the temp folder (<code>%s</code>) is not writable, check the "
569
- "permissions for this folder! Without having write access to this folder, the "
570
- "plugin will not be able to email invoices."
571
- msgstr ""
572
-
573
- #: includes/documents/class-wcpdf-invoice.php:168
574
- #: includes/views/setup-wizard/display-options.php:10
575
- msgid "Display shipping address"
576
- msgstr ""
577
-
578
- #: includes/documents/class-wcpdf-invoice.php:174
579
- msgid ""
580
- "Display shipping address (in addition to the default billing address) if "
581
- "different from billing address"
582
- msgstr ""
583
-
584
- #: includes/documents/class-wcpdf-invoice.php:180
585
- #: includes/documents/class-wcpdf-packing-slip.php:111
586
- #: includes/views/setup-wizard/display-options.php:12
587
- msgid "Display email address"
588
- msgstr ""
589
-
590
- #: includes/documents/class-wcpdf-invoice.php:191
591
- #: includes/documents/class-wcpdf-packing-slip.php:122
592
- #: includes/views/setup-wizard/display-options.php:14
593
- msgid "Display phone number"
594
- msgstr ""
595
-
596
- #: includes/documents/class-wcpdf-invoice.php:202
597
- #: includes/views/setup-wizard/display-options.php:16
598
- msgid "Display invoice date"
599
- msgstr ""
600
-
601
- #: includes/documents/class-wcpdf-invoice.php:214
602
- #: includes/views/setup-wizard/display-options.php:18
603
- msgid "Display invoice number"
604
- msgstr ""
605
-
606
- #: includes/documents/class-wcpdf-invoice.php:226
607
- msgid "Next invoice number (without prefix/suffix etc.)"
608
- msgstr ""
609
-
610
- #: includes/documents/class-wcpdf-invoice.php:232
611
- msgid ""
612
- "This is the number that will be used for the next document. By default, "
613
- "numbering starts from 1 and increases for every new document. Note that if "
614
- "you override this and set it lower than the current/highest number, this "
615
- "could create duplicate numbers!"
616
- msgstr ""
617
-
618
- #: includes/documents/class-wcpdf-invoice.php:238
619
- msgid "Number format"
620
- msgstr ""
621
-
622
- #: includes/documents/class-wcpdf-invoice.php:246
623
- msgid "Prefix"
624
- msgstr ""
625
-
626
- #: includes/documents/class-wcpdf-invoice.php:248
627
- msgid ""
628
- "to use the invoice year and/or month, use [invoice_year] or [invoice_month] "
629
- "respectively"
630
- msgstr ""
631
-
632
- #: includes/documents/class-wcpdf-invoice.php:251
633
- msgid "Suffix"
634
- msgstr ""
635
-
636
- #: includes/documents/class-wcpdf-invoice.php:256
637
- msgid "Padding"
638
- msgstr ""
639
-
640
- #: includes/documents/class-wcpdf-invoice.php:259
641
- msgid "enter the number of digits here - enter \"6\" to display 42 as 000042"
642
- msgstr ""
643
-
644
- #: includes/documents/class-wcpdf-invoice.php:262
645
- msgid ""
646
- "note: if you have already created a custom invoice number format with a "
647
- "filter, the above settings will be ignored"
648
- msgstr ""
649
-
650
- #: includes/documents/class-wcpdf-invoice.php:268
651
- msgid "Reset invoice number yearly"
652
- msgstr ""
653
-
654
- #: includes/documents/class-wcpdf-invoice.php:279
655
- msgid "Allow My Account invoice download"
656
- msgstr ""
657
-
658
- #: includes/documents/class-wcpdf-invoice.php:286
659
- msgid "Only when an invoice is already created/emailed"
660
- msgstr ""
661
-
662
- #: includes/documents/class-wcpdf-invoice.php:287
663
- msgid "Only for specific order statuses (define below)"
664
- msgstr ""
665
-
666
- #: includes/documents/class-wcpdf-invoice.php:288
667
- msgid "Always"
668
- msgstr ""
669
-
670
- #: includes/documents/class-wcpdf-invoice.php:289
671
- msgid "Never"
672
- msgstr ""
673
-
674
- #: includes/documents/class-wcpdf-invoice.php:304
675
- msgid "Enable invoice number column in the orders list"
676
- msgstr ""
677
-
678
- #: includes/documents/class-wcpdf-invoice.php:315
679
- msgid "Disable for free products"
680
- msgstr ""
681
-
682
- #: includes/documents/class-wcpdf-invoice.php:321
683
- msgid ""
684
- "Disable automatic creation/attachment when only free products are ordered"
685
- msgstr ""
686
-
687
- #: includes/documents/class-wcpdf-invoice.php:335
688
- msgid "Invoice numbers are created by a third-party extension."
689
- msgstr ""
690
-
691
- #: includes/documents/class-wcpdf-invoice.php:337
692
- #, php-format
693
- msgid "Configure it <a href=\"%s\">here</a>."
694
- msgstr ""
695
-
696
- #: includes/documents/class-wcpdf-packing-slip.php:32
697
- #: includes/documents/class-wcpdf-packing-slip.php:41
698
- #: includes/legacy/class-wcpdf-legacy-functions.php:25
699
- msgid "Packing Slip"
700
- msgstr ""
701
-
702
- #: includes/documents/class-wcpdf-packing-slip.php:47
703
- msgid "packing-slip"
704
- msgid_plural "packing-slips"
705
- msgstr[0] ""
706
- msgstr[1] ""
707
-
708
- #: includes/documents/class-wcpdf-packing-slip.php:99
709
- msgid "Display billing address"
710
- msgstr ""
711
-
712
- #: includes/documents/class-wcpdf-packing-slip.php:105
713
- msgid ""
714
- "Display billing address (in addition to the default shipping address) if "
715
- "different from shipping address"
716
- msgstr ""
717
-
718
- #: includes/legacy/class-wcpdf-legacy-document.php:32
719
- msgid "Legacy Document"
720
- msgstr ""
721
-
722
- #: includes/legacy/class-wcpdf-legacy.php:72
723
- msgid "Error"
724
- msgstr ""
725
-
726
- #: includes/legacy/class-wcpdf-legacy.php:73
727
- msgid ""
728
- "An outdated template or action hook was used to generate the PDF. Legacy "
729
- "mode has been activated, please try again by reloading this page."
730
- msgstr ""
731
-
732
- #: includes/legacy/class-wcpdf-legacy.php:76
733
- msgid "The following function was called"
734
- msgstr ""
735
-
736
- #: includes/views/attachment-settings-hint.php:21
737
- #, php-format
738
- msgid ""
739
- "It looks like you haven't setup any email attachments yet, check the "
740
- "settings under <b>%sDocuments > Invoice%s</b>"
741
- msgstr ""
742
-
743
- #: includes/views/setup-wizard/attach-to.php:2
744
- msgid "Attach too..."
745
- msgstr ""
746
-
747
- #: includes/views/setup-wizard/attach-to.php:3
748
- msgid "Select to which emails you would like to attach your invoice."
749
- msgstr ""
750
-
751
- #: includes/views/setup-wizard/display-options.php:3
752
- msgid "Select some additional display options for your invoice."
753
- msgstr ""
754
-
755
- #: includes/views/setup-wizard/good-to-go.php:2
756
- msgid "You are good to go!"
757
- msgstr ""
758
-
759
- #: includes/views/setup-wizard/good-to-go.php:3
760
- msgid "If you have any questions please have a look at our documentation:"
761
- msgstr ""
762
-
763
- #: includes/views/setup-wizard/good-to-go.php:4
764
- msgid "Invoices & Packing Slips"
765
- msgstr ""
766
-
767
- #: includes/views/setup-wizard/good-to-go.php:5
768
- msgid "Happy selling!"
769
- msgstr ""
770
-
771
- #: includes/views/setup-wizard/logo.php:3
772
- msgid "Set the header image that will display on your invoice."
773
- msgstr ""
774
-
775
- #: includes/views/setup-wizard/paper-format.php:3
776
- msgid "Select the paper format for your invoice."
777
- msgstr ""
778
-
779
- #: includes/views/setup-wizard/shop-name.php:2
780
- msgid "Enter your shop name"
781
- msgstr ""
782
-
783
- #: includes/views/setup-wizard/shop-name.php:3
784
- msgid ""
785
- "Lets quickly setup your invoice. Please enter the name and address of your "
786
- "shop in the fields on the right."
787
- msgstr ""
788
-
789
- #: includes/views/wcpdf-extensions.php:15
790
- msgid "Check out these premium extensions!"
791
- msgstr ""
792
-
793
- #: includes/views/wcpdf-extensions.php:16
794
- msgid "click items to read more"
795
- msgstr ""
796
-
797
- #: includes/views/wcpdf-extensions.php:21
798
- msgid ""
799
- "Premium PDF Invoice bundle: Everything you need for a perfect invoicing "
800
- "system"
801
- msgstr ""
802
-
803
- #: includes/views/wcpdf-extensions.php:23
804
- msgid ""
805
- "Supercharge WooCommerce PDF Invoices & Packing Slips with the all our "
806
- "premium extensions:"
807
- msgstr ""
808
-
809
- #: includes/views/wcpdf-extensions.php:24
810
- msgid "Professional features:"
811
- msgstr ""
812
-
813
- #: includes/views/wcpdf-extensions.php:26
814
- #: includes/views/wcpdf-extensions.php:56
815
- msgid "Email/print/download <b>PDF Credit Notes & Proforma invoices</b>"
816
- msgstr ""
817
-
818
- #: includes/views/wcpdf-extensions.php:27
819
- #: includes/views/wcpdf-extensions.php:57
820
- msgid ""
821
- "Send out a separate <b>notification email</b> with (or without) PDF invoices/"
822
- "packing slips, for example to a drop-shipper or a supplier."
823
- msgstr ""
824
-
825
- #: includes/views/wcpdf-extensions.php:28
826
- #: includes/views/wcpdf-extensions.php:58
827
- msgid ""
828
- "Attach <b>up to 3 static files</b> (for example a terms & conditions "
829
- "document) to the WooCommerce emails of your choice."
830
- msgstr ""
831
-
832
- #: includes/views/wcpdf-extensions.php:29
833
- #: includes/views/wcpdf-extensions.php:59
834
- msgid ""
835
- "Use <b>separate numbering systems</b> and/or format for proforma invoices "
836
- "and credit notes or utilize the main invoice numbering system"
837
- msgstr ""
838
-
839
- #: includes/views/wcpdf-extensions.php:30
840
- #: includes/views/wcpdf-extensions.php:60
841
- msgid ""
842
- "<b>Customize</b> the <b>shipping & billing address</b> format to include "
843
- "additional custom fields, font sizes etc. without the need to create a "
844
- "custom template."
845
- msgstr ""
846
-
847
- #: includes/views/wcpdf-extensions.php:31
848
- #: includes/views/wcpdf-extensions.php:61
849
- msgid "Use the plugin in multilingual <b>WPML</b> setups"
850
- msgstr ""
851
-
852
- #: includes/views/wcpdf-extensions.php:33
853
- #: includes/views/wcpdf-extensions.php:113
854
- msgid "Advanced, customizable templates"
855
- msgstr ""
856
-
857
- #: includes/views/wcpdf-extensions.php:35
858
- #: includes/views/wcpdf-extensions.php:116
859
- msgid ""
860
- "Completely customize the invoice contents (prices, taxes, thumbnails) to "
861
- "your needs with a drag & drop customizer"
862
- msgstr ""
863
-
864
- #: includes/views/wcpdf-extensions.php:36
865
- #: includes/views/wcpdf-extensions.php:117
866
- msgid "Two extra stylish premade templates (Modern & Business)"
867
- msgstr ""
868
-
869
- #: includes/views/wcpdf-extensions.php:38
870
- #: includes/views/wcpdf-extensions.php:62
871
- msgid "Upload automatically to dropbox"
872
- msgstr ""
873
-
874
- #: includes/views/wcpdf-extensions.php:40
875
- msgid ""
876
- "This extension conveniently uploads all the invoices (and other pdf "
877
- "documents from the professional extension) that are emailed to your "
878
- "customers to Dropbox. The best way to keep your invoice administration up to "
879
- "date!"
880
- msgstr ""
881
-
882
- #: includes/views/wcpdf-extensions.php:43
883
- msgid "Get WooCommerce PDF Invoices & Packing Slips Bundle"
884
- msgstr ""
885
-
886
- #: includes/views/wcpdf-extensions.php:52
887
- msgid "Go Pro: Proforma invoices, credit notes (=refunds) & more!"
888
- msgstr ""
889
-
890
- #: includes/views/wcpdf-extensions.php:54
891
- msgid ""
892
- "Supercharge WooCommerce PDF Invoices & Packing Slips with the following "
893
- "features:"
894
- msgstr ""
895
-
896
- #: includes/views/wcpdf-extensions.php:64
897
- msgid "Get WooCommerce PDF Invoices & Packing Slips Professional!"
898
- msgstr ""
899
-
900
- #: includes/views/wcpdf-extensions.php:72
901
- msgid "Automatically send payment reminders to your customers"
902
- msgstr ""
903
-
904
- #: includes/views/wcpdf-extensions.php:74
905
- msgid "WooCommerce Smart Reminder emails"
906
- msgstr ""
907
-
908
- #: includes/views/wcpdf-extensions.php:76
909
- msgid "<b>Completely automatic</b> scheduled emails"
910
- msgstr ""
911
-
912
- #: includes/views/wcpdf-extensions.php:77
913
- msgid ""
914
- "<b>Rich text editor</b> for the email text, including placeholders for data "
915
- "from the order (name, order total, etc)"
916
- msgstr ""
917
-
918
- #: includes/views/wcpdf-extensions.php:78
919
- msgid ""
920
- "Configure the exact requirements for sending an email (time after order, "
921
- "order status, payment method)"
922
- msgstr ""
923
-
924
- #: includes/views/wcpdf-extensions.php:79
925
- msgid ""
926
- "Fully <b>WPML Compatible</b> – emails will be automatically sent in the "
927
- "order language."
928
- msgstr ""
929
-
930
- #: includes/views/wcpdf-extensions.php:80
931
- msgid ""
932
- "<b>Super versatile!</b> Can be used for any kind of reminder email (review "
933
- "reminders, repeat purchases)"
934
- msgstr ""
935
-
936
- #: includes/views/wcpdf-extensions.php:81
937
- msgid "Integrates seamlessly with the PDF Invoices & Packing Slips plugin"
938
- msgstr ""
939
-
940
- #: includes/views/wcpdf-extensions.php:83
941
- msgid "Get WooCommerce Smart Reminder Emails"
942
- msgstr ""
943
-
944
- #: includes/views/wcpdf-extensions.php:92
945
- msgid ""
946
- "Automatically send new orders or packing slips to your printer, as soon as "
947
- "the customer orders!"
948
- msgstr ""
949
-
950
- #: includes/views/wcpdf-extensions.php:98
951
- msgid ""
952
- "Check out the WooCommerce Automatic Order Printing extension from our "
953
- "partners at Simba Hosting"
954
- msgstr ""
955
-
956
- #: includes/views/wcpdf-extensions.php:99
957
- msgid "WooCommerce Automatic Order Printing"
958
- msgstr ""
959
-
960
- #: includes/views/wcpdf-extensions.php:118
961
- #, php-format
962
- msgid "Check out the Premium PDF Invoice & Packing Slips templates at %s."
963
- msgstr ""
964
-
965
- #: includes/views/wcpdf-extensions.php:119
966
- #, php-format
967
- msgid "For custom templates, contact us at %s."
968
- msgstr ""
969
-
970
- #: includes/views/wcpdf-settings-page.php:8
971
- msgid "WooCommerce PDF Invoices"
972
- msgstr ""
973
-
974
- #: includes/wcpdf-functions.php:197
975
- msgid "Error creating PDF, please contact the site owner."
976
- msgstr ""
977
-
978
- #: templates/Simple/invoice.php:30 templates/Simple/packing-slip.php:43
979
- msgid "Billing Address:"
980
- msgstr ""
981
-
982
- #: templates/Simple/invoice.php:43
983
- msgid "Ship To:"
984
- msgstr ""
985
-
986
- #: templates/Simple/invoice.php:54
987
- msgid "Invoice Number:"
988
- msgstr ""
989
-
990
- #: templates/Simple/invoice.php:65 templates/Simple/packing-slip.php:53
991
- msgid "Order Number:"
992
- msgstr ""
993
-
994
- #: templates/Simple/invoice.php:69 templates/Simple/packing-slip.php:57
995
- msgid "Order Date:"
996
- msgstr ""
997
-
998
- #: templates/Simple/invoice.php:73
999
- msgid "Payment Method:"
1000
- msgstr ""
1001
-
1002
- #: templates/Simple/invoice.php:87 templates/Simple/packing-slip.php:75
1003
- msgid "Product"
1004
- msgstr ""
1005
-
1006
- #: templates/Simple/invoice.php:88 templates/Simple/packing-slip.php:76
1007
- msgid "Quantity"
1008
- msgstr ""
1009
-
1010
- #: templates/Simple/invoice.php:89
1011
- msgid "Price"
1012
- msgstr ""
1013
-
1014
- #: templates/Simple/invoice.php:96 templates/Simple/packing-slip.php:83
1015
- msgid "Description"
1016
- msgstr ""
1017
-
1018
- #: templates/Simple/invoice.php:101 templates/Simple/packing-slip.php:88
1019
- msgid "SKU"
1020
- msgstr ""
1021
-
1022
- #: templates/Simple/invoice.php:102 templates/Simple/packing-slip.php:89
1023
- msgid "SKU:"
1024
- msgstr ""
1025
-
1026
- #: templates/Simple/invoice.php:103 templates/Simple/packing-slip.php:90
1027
- msgid "Weight:"
1028
- msgstr ""
1029
-
1030
- #: templates/Simple/invoice.php:118 templates/Simple/packing-slip.php:105
1031
- msgid "Customer Notes"
1032
- msgstr ""
1033
-
1034
- #: templates/Simple/packing-slip.php:30
1035
- msgid "Shipping Address:"
1036
- msgstr ""
1037
-
1038
- #: templates/Simple/packing-slip.php:61
1039
- msgid "Shipping Method:"
1040
- msgstr ""
1041
-
1042
- #: woocommerce-pdf-invoices-packingslips.php:237
1043
- #, php-format
1044
- msgid ""
1045
- "WooCommerce PDF Invoices & Packing Slips requires %sWooCommerce%s to be "
1046
- "installed & activated!"
1047
- msgstr ""
1048
-
1049
- #: woocommerce-pdf-invoices-packingslips.php:249
1050
- msgid ""
1051
- "WooCommerce PDF Invoices & Packing Slips requires PHP 5.3 or higher (5.6 or "
1052
- "higher recommended)."
1053
- msgstr ""
1054
-
1055
- #: woocommerce-pdf-invoices-packingslips.php:250
1056
- msgid "How to update your PHP version"
1057
- msgstr ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #, fuzzy
2
+ msgid ""
3
+ msgstr ""
4
+ "Project-Id-Version: WooCommerce PDF Invoices & Packing Slips\n"
5
+ "POT-Creation-Date: 2020-06-04 16:08+0200\n"
6
+ "PO-Revision-Date: 2015-04-29 08:58+0100\n"
7
+ "Last-Translator: \n"
8
+ "Language-Team: WP Overnight <support@wpovernight.com>\n"
9
+ "Language: en_US\n"
10
+ "MIME-Version: 1.0\n"
11
+ "Content-Type: text/plain; charset=UTF-8\n"
12
+ "Content-Transfer-Encoding: 8bit\n"
13
+ "X-Generator: Poedit 2.2.1\n"
14
+ "X-Poedit-Basepath: ..\n"
15
+ "Plural-Forms: nplurals=2; plural=n != 1;\n"
16
+ "X-Poedit-SourceCharset: UTF-8\n"
17
+ "X-Poedit-KeywordsList: __;_e;_x;_n:1,2\n"
18
+ "X-Poedit-SearchPath-0: .\n"
19
+
20
+ #: includes/class-wcpdf-admin.php:90
21
+ #, php-format
22
+ msgid "Wow, you have created more than %d invoices with our plugin!"
23
+ msgstr ""
24
+
25
+ #: includes/class-wcpdf-admin.php:91
26
+ msgid ""
27
+ "It would mean a lot to us if you would quickly give our plugin a 5-star "
28
+ "rating. Help us spread the word and boost our motivation!"
29
+ msgstr ""
30
+
31
+ #: includes/class-wcpdf-admin.php:93
32
+ msgid "Yes you deserve it!"
33
+ msgstr ""
34
+
35
+ #: includes/class-wcpdf-admin.php:94
36
+ #: includes/views/attachment-settings-hint.php:23
37
+ #: includes/views/wcpdf-extensions.php:129
38
+ msgid "Hide this message"
39
+ msgstr ""
40
+
41
+ #: includes/class-wcpdf-admin.php:94
42
+ msgid "Already did!"
43
+ msgstr ""
44
+
45
+ #: includes/class-wcpdf-admin.php:95
46
+ msgid "Actually, I have a complaint..."
47
+ msgstr ""
48
+
49
+ #: includes/class-wcpdf-admin.php:130
50
+ msgid "New to WooCommerce PDF Invoices & Packing Slips?"
51
+ msgstr ""
52
+
53
+ #: includes/class-wcpdf-admin.php:130
54
+ msgid "Jumpstart the plugin by following our wizard!"
55
+ msgstr ""
56
+
57
+ #: includes/class-wcpdf-admin.php:131
58
+ msgid "Run the Setup Wizard"
59
+ msgstr ""
60
+
61
+ #: includes/class-wcpdf-admin.php:131
62
+ msgid "I am the wizard"
63
+ msgstr ""
64
+
65
+ #: includes/class-wcpdf-admin.php:219 includes/class-wcpdf-admin.php:391
66
+ #: includes/class-wcpdf-main.php:700
67
+ #: includes/documents/class-wcpdf-invoice.php:271
68
+ msgid "Invoice Number"
69
+ msgstr ""
70
+
71
+ #: includes/class-wcpdf-admin.php:256
72
+ msgid "Send order email"
73
+ msgstr ""
74
+
75
+ #: includes/class-wcpdf-admin.php:267
76
+ msgid "Create PDF"
77
+ msgstr ""
78
+
79
+ #: includes/class-wcpdf-admin.php:277
80
+ msgid "PDF Invoice data"
81
+ msgstr ""
82
+
83
+ #: includes/class-wcpdf-admin.php:317
84
+ msgid "Send email"
85
+ msgstr ""
86
+
87
+ #: includes/class-wcpdf-admin.php:400 includes/class-wcpdf-admin.php:424
88
+ #: templates/Simple/invoice.php:61
89
+ msgid "Invoice Date:"
90
+ msgstr ""
91
+
92
+ #: includes/class-wcpdf-admin.php:409
93
+ msgid "Set invoice number & date"
94
+ msgstr ""
95
+
96
+ #: includes/class-wcpdf-admin.php:416
97
+ msgid "Invoice Number (unformatted!)"
98
+ msgstr ""
99
+
100
+ #: includes/class-wcpdf-admin.php:426 includes/class-wcpdf-admin.php:428
101
+ msgid "h"
102
+ msgstr ""
103
+
104
+ #: includes/class-wcpdf-admin.php:426 includes/class-wcpdf-admin.php:428
105
+ msgid "m"
106
+ msgstr ""
107
+
108
+ #: includes/class-wcpdf-admin.php:570
109
+ #, php-format
110
+ msgid "%s email notification manually sent."
111
+ msgstr ""
112
+
113
+ #: includes/class-wcpdf-admin.php:734
114
+ msgid "DEBUG output enabled"
115
+ msgstr ""
116
+
117
+ #: includes/class-wcpdf-assets.php:91
118
+ msgid "Are you sure you want to delete this document? This cannot be undone."
119
+ msgstr ""
120
+
121
+ #: includes/class-wcpdf-assets.php:92
122
+ msgid ""
123
+ "Are you sure you want to regenerate this document? This will make the "
124
+ "document reflect the most current settings (such as footer text, document "
125
+ "name, etc.) rather than using historical settings."
126
+ msgstr ""
127
+
128
+ #: includes/class-wcpdf-frontend.php:110
129
+ msgid "Download invoice (PDF)"
130
+ msgstr ""
131
+
132
+ #: includes/class-wcpdf-main.php:241 includes/class-wcpdf-main.php:246
133
+ #: includes/class-wcpdf-main.php:316
134
+ msgid "You do not have sufficient permissions to access this page."
135
+ msgstr ""
136
+
137
+ #: includes/class-wcpdf-main.php:255
138
+ msgid "You haven't selected any orders"
139
+ msgstr ""
140
+
141
+ #: includes/class-wcpdf-main.php:259
142
+ msgid "Some of the export parameters are missing."
143
+ msgstr ""
144
+
145
+ #: includes/class-wcpdf-main.php:344
146
+ #, php-format
147
+ msgid "Document of type '%s' for the selected order(s) could not be generated"
148
+ msgstr ""
149
+
150
+ #: includes/class-wcpdf-main.php:701
151
+ #: includes/documents/class-wcpdf-invoice.php:255
152
+ msgid "Invoice Date"
153
+ msgstr ""
154
+
155
+ #: includes/class-wcpdf-settings-callbacks.php:27
156
+ msgid ""
157
+ "<b>Warning!</b> The settings below are meant for debugging/development only. "
158
+ "Do not use them on a live website!"
159
+ msgstr ""
160
+
161
+ #: includes/class-wcpdf-settings-callbacks.php:36
162
+ msgid ""
163
+ "These are used for the (optional) footer columns in the <em>Modern "
164
+ "(Premium)</em> template, but can also be used for other elements in your "
165
+ "custom template"
166
+ msgstr ""
167
+
168
+ #: includes/class-wcpdf-settings-callbacks.php:378
169
+ msgid "Image resolution"
170
+ msgstr ""
171
+
172
+ #: includes/class-wcpdf-settings-callbacks.php:405
173
+ msgid "Save"
174
+ msgstr ""
175
+
176
+ #: includes/class-wcpdf-settings-debug.php:42
177
+ msgid "Reinstall fonts"
178
+ msgstr ""
179
+
180
+ #: includes/class-wcpdf-settings-debug.php:63
181
+ msgid "Fonts reinstalled!"
182
+ msgstr ""
183
+
184
+ #: includes/class-wcpdf-settings-debug.php:70
185
+ msgid "Remove temporary files"
186
+ msgstr ""
187
+
188
+ #: includes/class-wcpdf-settings-debug.php:81
189
+ msgid "Unable to read temporary folder contents!"
190
+ msgstr ""
191
+
192
+ #: includes/class-wcpdf-settings-debug.php:98
193
+ #, php-format
194
+ msgid "Unable to delete %d files! (deleted %d)"
195
+ msgstr ""
196
+
197
+ #: includes/class-wcpdf-settings-debug.php:101
198
+ #, php-format
199
+ msgid "Successfully deleted %d files!"
200
+ msgstr ""
201
+
202
+ #: includes/class-wcpdf-settings-debug.php:105
203
+ msgid "Nothing to delete!"
204
+ msgstr ""
205
+
206
+ #: includes/class-wcpdf-settings-debug.php:114
207
+ msgid "Delete legacy (1.X) settings"
208
+ msgstr ""
209
+
210
+ #: includes/class-wcpdf-settings-debug.php:130
211
+ msgid "Legacy settings deleted!"
212
+ msgstr ""
213
+
214
+ #: includes/class-wcpdf-settings-debug.php:157
215
+ msgid "Debug settings"
216
+ msgstr ""
217
+
218
+ #: includes/class-wcpdf-settings-debug.php:163
219
+ msgid "Legacy mode"
220
+ msgstr ""
221
+
222
+ #: includes/class-wcpdf-settings-debug.php:169
223
+ msgid ""
224
+ "Legacy mode ensures compatibility with templates and filters from previous "
225
+ "versions."
226
+ msgstr ""
227
+
228
+ #: includes/class-wcpdf-settings-debug.php:175
229
+ msgid "Allow guest access"
230
+ msgstr ""
231
+
232
+ #: includes/class-wcpdf-settings-debug.php:181
233
+ msgid ""
234
+ "Enable this to allow customers that purchase without an account to access "
235
+ "their PDF with a unique key"
236
+ msgstr ""
237
+
238
+ #: includes/class-wcpdf-settings-debug.php:187
239
+ msgid "Calculate document numbers (slow)"
240
+ msgstr ""
241
+
242
+ #: includes/class-wcpdf-settings-debug.php:193
243
+ msgid ""
244
+ "Document numbers (such as invoice numbers) are generated using "
245
+ "AUTO_INCREMENT by default. Use this setting if your database auto increments "
246
+ "with more than 1."
247
+ msgstr ""
248
+
249
+ #: includes/class-wcpdf-settings-debug.php:199
250
+ msgid "Enable debug output"
251
+ msgstr ""
252
+
253
+ #: includes/class-wcpdf-settings-debug.php:205
254
+ msgid ""
255
+ "Enable this option to output plugin errors if you're getting a blank page or "
256
+ "other PDF generation issues"
257
+ msgstr ""
258
+
259
+ #: includes/class-wcpdf-settings-debug.php:206
260
+ msgid ""
261
+ "<b>Caution!</b> This setting may reveal errors (from other plugins) in other "
262
+ "places on your site too, therefor this is not recommended to leave it "
263
+ "enabled on live sites."
264
+ msgstr ""
265
+
266
+ #: includes/class-wcpdf-settings-debug.php:207
267
+ msgid ""
268
+ "You can also add <code>&debug=true</code> to the URL to apply this on a per-"
269
+ "order basis."
270
+ msgstr ""
271
+
272
+ #: includes/class-wcpdf-settings-debug.php:213
273
+ msgid "Enable automatic cleanup"
274
+ msgstr ""
275
+
276
+ #: includes/class-wcpdf-settings-debug.php:220
277
+ #, php-format
278
+ msgid "every %s days"
279
+ msgstr ""
280
+
281
+ #: includes/class-wcpdf-settings-debug.php:225
282
+ msgid ""
283
+ "Automatically clean up PDF files stored in the temporary folder (used for "
284
+ "email attachments)"
285
+ msgstr ""
286
+
287
+ #: includes/class-wcpdf-settings-debug.php:226
288
+ msgid ""
289
+ "<b>Disabled:</b> The PHP functions glob and filemtime are required for "
290
+ "automatic cleanup but not enabled on your server."
291
+ msgstr ""
292
+
293
+ #: includes/class-wcpdf-settings-debug.php:232
294
+ msgid "Output to HTML"
295
+ msgstr ""
296
+
297
+ #: includes/class-wcpdf-settings-debug.php:238
298
+ msgid ""
299
+ "Send the template output as HTML to the browser instead of creating a PDF."
300
+ msgstr ""
301
+
302
+ #: includes/class-wcpdf-settings-debug.php:239
303
+ msgid ""
304
+ "You can also add <code>&output=html</code> to the URL to apply this on a per-"
305
+ "order basis."
306
+ msgstr ""
307
+
308
+ #: includes/class-wcpdf-settings-debug.php:245
309
+ msgid "Use alternative HTML5 parser to parse HTML"
310
+ msgstr ""
311
+
312
+ #: includes/class-wcpdf-settings-documents.php:30
313
+ #: includes/class-wcpdf-settings.php:96
314
+ msgid "Documents"
315
+ msgstr ""
316
+
317
+ #: includes/class-wcpdf-settings-documents.php:36
318
+ #: includes/class-wcpdf-settings-documents.php:57
319
+ msgid "untitled"
320
+ msgstr ""
321
+
322
+ #: includes/class-wcpdf-settings-documents.php:50
323
+ msgid ""
324
+ "All available documents are listed below. Click on a document to configure "
325
+ "it."
326
+ msgstr ""
327
+
328
+ #: includes/class-wcpdf-settings-general.php:40
329
+ msgid "General settings"
330
+ msgstr ""
331
+
332
+ #: includes/class-wcpdf-settings-general.php:46
333
+ msgid "How do you want to view the PDF?"
334
+ msgstr ""
335
+
336
+ #: includes/class-wcpdf-settings-general.php:53
337
+ msgid "Download the PDF"
338
+ msgstr ""
339
+
340
+ #: includes/class-wcpdf-settings-general.php:54
341
+ msgid "Open the PDF in a new browser tab/window"
342
+ msgstr ""
343
+
344
+ #: includes/class-wcpdf-settings-general.php:61
345
+ msgid "Choose a template"
346
+ msgstr ""
347
+
348
+ #: includes/class-wcpdf-settings-general.php:68
349
+ #, php-format
350
+ msgid ""
351
+ "Want to use your own template? Copy all the files from <code>%s</code> to "
352
+ "your (child) theme in <code>%s</code> to customize them"
353
+ msgstr ""
354
+
355
+ #: includes/class-wcpdf-settings-general.php:74
356
+ msgid "Paper size"
357
+ msgstr ""
358
+
359
+ #: includes/class-wcpdf-settings-general.php:81
360
+ #: includes/views/setup-wizard/paper-format.php:12
361
+ msgid "A4"
362
+ msgstr ""
363
+
364
+ #: includes/class-wcpdf-settings-general.php:82
365
+ #: includes/views/setup-wizard/paper-format.php:13
366
+ msgid "Letter"
367
+ msgstr ""
368
+
369
+ #: includes/class-wcpdf-settings-general.php:89
370
+ msgid "Test mode"
371
+ msgstr ""
372
+
373
+ #: includes/class-wcpdf-settings-general.php:95
374
+ msgid ""
375
+ "With test mode enabled, any document generated will always use the latest "
376
+ "settings, rather than using the settings as configured at the time the "
377
+ "document was first created."
378
+ msgstr ""
379
+
380
+ #: includes/class-wcpdf-settings-general.php:95
381
+ msgid ""
382
+ "<strong>Note:</strong> invoice numbers and dates are not affected by this "
383
+ "setting and will still be generated."
384
+ msgstr ""
385
+
386
+ #: includes/class-wcpdf-settings-general.php:101
387
+ msgid "Extended currency symbol support"
388
+ msgstr ""
389
+
390
+ #: includes/class-wcpdf-settings-general.php:107
391
+ msgid "Enable this if your currency symbol is not displaying properly"
392
+ msgstr ""
393
+
394
+ #: includes/class-wcpdf-settings-general.php:113
395
+ msgid "Enable font subsetting"
396
+ msgstr ""
397
+
398
+ #: includes/class-wcpdf-settings-general.php:119
399
+ msgid ""
400
+ "Font subsetting can reduce file size by only including the characters that "
401
+ "are used in the PDF, but limits the ability to edit PDF files later. "
402
+ "Recommended if you're using an Asian font."
403
+ msgstr ""
404
+
405
+ #: includes/class-wcpdf-settings-general.php:125
406
+ msgid "Shop header/logo"
407
+ msgstr ""
408
+
409
+ #: includes/class-wcpdf-settings-general.php:131
410
+ #: includes/views/setup-wizard/logo.php:20
411
+ msgid "Select or upload your invoice header/logo"
412
+ msgstr ""
413
+
414
+ #: includes/class-wcpdf-settings-general.php:132
415
+ #: includes/views/setup-wizard/logo.php:20
416
+ msgid "Set image"
417
+ msgstr ""
418
+
419
+ #: includes/class-wcpdf-settings-general.php:133
420
+ #: includes/views/setup-wizard/logo.php:20
421
+ msgid "Remove image"
422
+ msgstr ""
423
+
424
+ #: includes/class-wcpdf-settings-general.php:140
425
+ msgid "Logo height"
426
+ msgstr ""
427
+
428
+ #: includes/class-wcpdf-settings-general.php:148
429
+ msgid ""
430
+ "Enter the total height of the logo in mm, cm or in and use a dot for "
431
+ "decimals.<br/>For example: 1.15in or 40mm"
432
+ msgstr ""
433
+
434
+ #: includes/class-wcpdf-settings-general.php:154
435
+ #: includes/class-wcpdf-setup-wizard.php:42
436
+ msgid "Shop Name"
437
+ msgstr ""
438
+
439
+ #: includes/class-wcpdf-settings-general.php:167
440
+ msgid "Shop Address"
441
+ msgstr ""
442
+
443
+ #: includes/class-wcpdf-settings-general.php:182
444
+ msgid "Footer: terms & conditions, policies, etc."
445
+ msgstr ""
446
+
447
+ #: includes/class-wcpdf-settings-general.php:197
448
+ msgid "Extra template fields"
449
+ msgstr ""
450
+
451
+ #: includes/class-wcpdf-settings-general.php:203
452
+ msgid "Extra field 1"
453
+ msgstr ""
454
+
455
+ #: includes/class-wcpdf-settings-general.php:211
456
+ msgid "This is footer column 1 in the <i>Modern (Premium)</i> template"
457
+ msgstr ""
458
+
459
+ #: includes/class-wcpdf-settings-general.php:218
460
+ msgid "Extra field 2"
461
+ msgstr ""
462
+
463
+ #: includes/class-wcpdf-settings-general.php:226
464
+ msgid "This is footer column 2 in the <i>Modern (Premium)</i> template"
465
+ msgstr ""
466
+
467
+ #: includes/class-wcpdf-settings-general.php:233
468
+ msgid "Extra field 3"
469
+ msgstr ""
470
+
471
+ #: includes/class-wcpdf-settings-general.php:241
472
+ msgid "This is footer column 3 in the <i>Modern (Premium)</i> template"
473
+ msgstr ""
474
+
475
+ #: includes/class-wcpdf-settings.php:48 includes/class-wcpdf-settings.php:49
476
+ msgid "PDF Invoices"
477
+ msgstr ""
478
+
479
+ #: includes/class-wcpdf-settings.php:61
480
+ msgid "Settings"
481
+ msgstr ""
482
+
483
+ #: includes/class-wcpdf-settings.php:74
484
+ msgid "Documentation"
485
+ msgstr ""
486
+
487
+ #: includes/class-wcpdf-settings.php:75
488
+ msgid "Support Forum"
489
+ msgstr ""
490
+
491
+ #: includes/class-wcpdf-settings.php:87
492
+ #, php-format
493
+ msgid ""
494
+ "<strong>Warning!</strong> Your database has an AUTO_INCREMENT step size of "
495
+ "%s, your invoice numbers may not be sequential. Enable the 'Calculate "
496
+ "document numbers (slow)' setting in the Status tab to use an alternate "
497
+ "method."
498
+ msgstr ""
499
+
500
+ #: includes/class-wcpdf-settings.php:95
501
+ msgid "General"
502
+ msgstr ""
503
+
504
+ #: includes/class-wcpdf-settings.php:101
505
+ msgid "Status"
506
+ msgstr ""
507
+
508
+ #: includes/class-wcpdf-setup-wizard.php:46
509
+ #: includes/views/setup-wizard/logo.php:3
510
+ msgid "Your logo"
511
+ msgstr ""
512
+
513
+ #: includes/class-wcpdf-setup-wizard.php:50
514
+ msgid "Attachments"
515
+ msgstr ""
516
+
517
+ #: includes/class-wcpdf-setup-wizard.php:54
518
+ #: includes/views/setup-wizard/display-options.php:3
519
+ msgid "Display options"
520
+ msgstr ""
521
+
522
+ #: includes/class-wcpdf-setup-wizard.php:58
523
+ #: includes/views/setup-wizard/paper-format.php:3
524
+ msgid "Paper format"
525
+ msgstr ""
526
+
527
+ #: includes/class-wcpdf-setup-wizard.php:62
528
+ #: includes/views/setup-wizard/show-action-buttons.php:3
529
+ msgid "Action buttons"
530
+ msgstr ""
531
+
532
+ #: includes/class-wcpdf-setup-wizard.php:66
533
+ msgid "Ready!"
534
+ msgstr ""
535
+
536
+ #: includes/class-wcpdf-setup-wizard.php:180
537
+ msgid "Previous"
538
+ msgstr ""
539
+
540
+ #: includes/class-wcpdf-setup-wizard.php:186
541
+ msgid "Skip this step"
542
+ msgstr ""
543
+
544
+ #: includes/class-wcpdf-setup-wizard.php:188
545
+ msgid "Finish"
546
+ msgstr ""
547
+
548
+ #: includes/compatibility/class-wc-core-compatibility.php:222
549
+ msgid "WooCommerce"
550
+ msgstr ""
551
+
552
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:116
553
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:183
554
+ msgid "N/A"
555
+ msgstr ""
556
+
557
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:424
558
+ msgid "Payment method"
559
+ msgstr ""
560
+
561
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:445
562
+ msgid "Shipping method"
563
+ msgstr ""
564
+
565
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:888
566
+ #, php-format
567
+ msgid "(includes %s)"
568
+ msgstr ""
569
+
570
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:891
571
+ #, php-format
572
+ msgid "(Includes %s)"
573
+ msgstr ""
574
+
575
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:921
576
+ msgid "Subtotal"
577
+ msgstr ""
578
+
579
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:946
580
+ msgid "Shipping"
581
+ msgstr ""
582
+
583
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:1009
584
+ msgid "Discount"
585
+ msgstr ""
586
+
587
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:1050
588
+ msgid "VAT"
589
+ msgstr ""
590
+
591
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:1051
592
+ msgid "Tax rate"
593
+ msgstr ""
594
+
595
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:1095
596
+ msgid "Total ex. VAT"
597
+ msgstr ""
598
+
599
+ #: includes/documents/abstract-wcpdf-order-document-methods.php:1098
600
+ msgid "Total"
601
+ msgstr ""
602
+
603
+ #: includes/documents/abstract-wcpdf-order-document.php:323
604
+ #, php-format
605
+ msgid "%s (refund #%s) was regenerated."
606
+ msgstr ""
607
+
608
+ #: includes/documents/abstract-wcpdf-order-document.php:323
609
+ #, php-format
610
+ msgid "%s was regenerated"
611
+ msgstr ""
612
+
613
+ #: includes/documents/abstract-wcpdf-order-document.php:837
614
+ msgid "Admin email"
615
+ msgstr ""
616
+
617
+ #: includes/documents/abstract-wcpdf-order-document.php:840
618
+ msgid "Manual email"
619
+ msgstr ""
620
+
621
+ #: includes/documents/class-wcpdf-invoice.php:32
622
+ #: includes/documents/class-wcpdf-invoice.php:56
623
+ #: includes/legacy/class-wcpdf-legacy-functions.php:22
624
+ msgid "Invoice"
625
+ msgstr ""
626
+
627
+ #: includes/documents/class-wcpdf-invoice.php:129
628
+ msgid "invoice"
629
+ msgid_plural "invoices"
630
+ msgstr[0] ""
631
+ msgstr[1] ""
632
+
633
+ #: includes/documents/class-wcpdf-invoice.php:174
634
+ #: includes/documents/class-wcpdf-packing-slip.php:88
635
+ msgid "Enable"
636
+ msgstr ""
637
+
638
+ #: includes/documents/class-wcpdf-invoice.php:185
639
+ msgid "Attach to:"
640
+ msgstr ""
641
+
642
+ #: includes/documents/class-wcpdf-invoice.php:192
643
+ #, php-format
644
+ msgid ""
645
+ "It looks like the temp folder (<code>%s</code>) is not writable, check the "
646
+ "permissions for this folder! Without having write access to this folder, the "
647
+ "plugin will not be able to email invoices."
648
+ msgstr ""
649
+
650
+ #: includes/documents/class-wcpdf-invoice.php:198
651
+ msgid "Disable for:"
652
+ msgstr ""
653
+
654
+ #: includes/documents/class-wcpdf-invoice.php:207
655
+ msgid "Select one or more statuses"
656
+ msgstr ""
657
+
658
+ #: includes/documents/class-wcpdf-invoice.php:213
659
+ #: includes/views/setup-wizard/display-options.php:17
660
+ msgid "Display shipping address"
661
+ msgstr ""
662
+
663
+ #: includes/documents/class-wcpdf-invoice.php:219
664
+ msgid ""
665
+ "Display shipping address (in addition to the default billing address) if "
666
+ "different from billing address"
667
+ msgstr ""
668
+
669
+ #: includes/documents/class-wcpdf-invoice.php:225
670
+ #: includes/documents/class-wcpdf-packing-slip.php:111
671
+ #: includes/views/setup-wizard/display-options.php:26
672
+ msgid "Display email address"
673
+ msgstr ""
674
+
675
+ #: includes/documents/class-wcpdf-invoice.php:236
676
+ #: includes/documents/class-wcpdf-packing-slip.php:122
677
+ #: includes/views/setup-wizard/display-options.php:35
678
+ msgid "Display phone number"
679
+ msgstr ""
680
+
681
+ #: includes/documents/class-wcpdf-invoice.php:247
682
+ #: includes/views/setup-wizard/display-options.php:44
683
+ msgid "Display invoice date"
684
+ msgstr ""
685
+
686
+ #: includes/documents/class-wcpdf-invoice.php:254
687
+ #: includes/documents/class-wcpdf-invoice.php:270
688
+ msgid "No"
689
+ msgstr ""
690
+
691
+ #: includes/documents/class-wcpdf-invoice.php:256
692
+ msgid "Order Date"
693
+ msgstr ""
694
+
695
+ #: includes/documents/class-wcpdf-invoice.php:263
696
+ #: includes/views/setup-wizard/display-options.php:53
697
+ msgid "Display invoice number"
698
+ msgstr ""
699
+
700
+ #: includes/documents/class-wcpdf-invoice.php:272
701
+ msgid "Order Number"
702
+ msgstr ""
703
+
704
+ #: includes/documents/class-wcpdf-invoice.php:276
705
+ msgid "Warning!"
706
+ msgstr ""
707
+
708
+ #: includes/documents/class-wcpdf-invoice.php:277
709
+ msgid ""
710
+ "Using the Order Number as invoice number is not recommended as this may lead "
711
+ "to gaps in the invoice number sequence (even when order numbers are "
712
+ "sequential)."
713
+ msgstr ""
714
+
715
+ #: includes/documents/class-wcpdf-invoice.php:278
716
+ msgid "More information"
717
+ msgstr ""
718
+
719
+ #: includes/documents/class-wcpdf-invoice.php:285
720
+ msgid "Next invoice number (without prefix/suffix etc.)"
721
+ msgstr ""
722
+
723
+ #: includes/documents/class-wcpdf-invoice.php:291
724
+ msgid ""
725
+ "This is the number that will be used for the next document. By default, "
726
+ "numbering starts from 1 and increases for every new document. Note that if "
727
+ "you override this and set it lower than the current/highest number, this "
728
+ "could create duplicate numbers!"
729
+ msgstr ""
730
+
731
+ #: includes/documents/class-wcpdf-invoice.php:297
732
+ msgid "Number format"
733
+ msgstr ""
734
+
735
+ #: includes/documents/class-wcpdf-invoice.php:305
736
+ msgid "Prefix"
737
+ msgstr ""
738
+
739
+ #: includes/documents/class-wcpdf-invoice.php:307
740
+ msgid ""
741
+ "to use the invoice year and/or month, use [invoice_year] or [invoice_month] "
742
+ "respectively"
743
+ msgstr ""
744
+
745
+ #: includes/documents/class-wcpdf-invoice.php:310
746
+ msgid "Suffix"
747
+ msgstr ""
748
+
749
+ #: includes/documents/class-wcpdf-invoice.php:315
750
+ msgid "Padding"
751
+ msgstr ""
752
+
753
+ #: includes/documents/class-wcpdf-invoice.php:318
754
+ msgid "enter the number of digits here - enter \"6\" to display 42 as 000042"
755
+ msgstr ""
756
+
757
+ #: includes/documents/class-wcpdf-invoice.php:321
758
+ msgid ""
759
+ "note: if you have already created a custom invoice number format with a "
760
+ "filter, the above settings will be ignored"
761
+ msgstr ""
762
+
763
+ #: includes/documents/class-wcpdf-invoice.php:327
764
+ msgid "Reset invoice number yearly"
765
+ msgstr ""
766
+
767
+ #: includes/documents/class-wcpdf-invoice.php:338
768
+ msgid "Allow My Account invoice download"
769
+ msgstr ""
770
+
771
+ #: includes/documents/class-wcpdf-invoice.php:345
772
+ msgid "Only when an invoice is already created/emailed"
773
+ msgstr ""
774
+
775
+ #: includes/documents/class-wcpdf-invoice.php:346
776
+ msgid "Only for specific order statuses (define below)"
777
+ msgstr ""
778
+
779
+ #: includes/documents/class-wcpdf-invoice.php:347
780
+ msgid "Always"
781
+ msgstr ""
782
+
783
+ #: includes/documents/class-wcpdf-invoice.php:348
784
+ msgid "Never"
785
+ msgstr ""
786
+
787
+ #: includes/documents/class-wcpdf-invoice.php:363
788
+ msgid "Enable invoice number column in the orders list"
789
+ msgstr ""
790
+
791
+ #: includes/documents/class-wcpdf-invoice.php:374
792
+ msgid "Disable for free orders"
793
+ msgstr ""
794
+
795
+ #: includes/documents/class-wcpdf-invoice.php:380
796
+ #, php-format
797
+ msgid "Disable automatic creation/attachment when the order total is %s"
798
+ msgstr ""
799
+
800
+ #: includes/documents/class-wcpdf-invoice.php:386
801
+ msgid "Always use most current settings"
802
+ msgstr ""
803
+
804
+ #: includes/documents/class-wcpdf-invoice.php:392
805
+ msgid ""
806
+ "When enabled, the document will always reflect the most current settings "
807
+ "(such as footer text, document name, etc.) rather than using historical "
808
+ "settings."
809
+ msgstr ""
810
+
811
+ #: includes/documents/class-wcpdf-invoice.php:394
812
+ msgid ""
813
+ "<strong>Caution:</strong> enabling this will also mean that if you change "
814
+ "your company name or address in the future, previously generated documents "
815
+ "will also be affected."
816
+ msgstr ""
817
+
818
+ #: includes/documents/class-wcpdf-invoice.php:407
819
+ msgid "Invoice numbers are created by a third-party extension."
820
+ msgstr ""
821
+
822
+ #: includes/documents/class-wcpdf-invoice.php:409
823
+ #, php-format
824
+ msgid "Configure it <a href=\"%s\">here</a>."
825
+ msgstr ""
826
+
827
+ #: includes/documents/class-wcpdf-packing-slip.php:32
828
+ #: includes/documents/class-wcpdf-packing-slip.php:41
829
+ #: includes/legacy/class-wcpdf-legacy-functions.php:25
830
+ msgid "Packing Slip"
831
+ msgstr ""
832
+
833
+ #: includes/documents/class-wcpdf-packing-slip.php:47
834
+ msgid "packing-slip"
835
+ msgid_plural "packing-slips"
836
+ msgstr[0] ""
837
+ msgstr[1] ""
838
+
839
+ #: includes/documents/class-wcpdf-packing-slip.php:99
840
+ msgid "Display billing address"
841
+ msgstr ""
842
+
843
+ #: includes/documents/class-wcpdf-packing-slip.php:105
844
+ msgid ""
845
+ "Display billing address (in addition to the default shipping address) if "
846
+ "different from shipping address"
847
+ msgstr ""
848
+
849
+ #: includes/legacy/class-wcpdf-legacy-document.php:32
850
+ msgid "Legacy Document"
851
+ msgstr ""
852
+
853
+ #: includes/legacy/class-wcpdf-legacy.php:72
854
+ msgid "Error"
855
+ msgstr ""
856
+
857
+ #: includes/legacy/class-wcpdf-legacy.php:73
858
+ msgid ""
859
+ "An outdated template or action hook was used to generate the PDF. Legacy "
860
+ "mode has been activated, please try again by reloading this page."
861
+ msgstr ""
862
+
863
+ #: includes/legacy/class-wcpdf-legacy.php:76
864
+ msgid "The following function was called"
865
+ msgstr ""
866
+
867
+ #: includes/views/attachment-settings-hint.php:22
868
+ #, php-format
869
+ msgid ""
870
+ "It looks like you haven't setup any email attachments yet, check the "
871
+ "settings under <b>%sDocuments > Invoice%s</b>"
872
+ msgstr ""
873
+
874
+ #: includes/views/setup-wizard/attach-to.php:3
875
+ msgid "Attach too..."
876
+ msgstr ""
877
+
878
+ #: includes/views/setup-wizard/attach-to.php:4
879
+ msgid "Select to which emails you would like to attach your invoice."
880
+ msgstr ""
881
+
882
+ #: includes/views/setup-wizard/display-options.php:4
883
+ msgid "Select some additional display options for your invoice."
884
+ msgstr ""
885
+
886
+ #: includes/views/setup-wizard/good-to-go.php:3
887
+ msgid "You are good to go!"
888
+ msgstr ""
889
+
890
+ #: includes/views/setup-wizard/good-to-go.php:4
891
+ msgid "If you have any questions please have a look at our documentation:"
892
+ msgstr ""
893
+
894
+ #: includes/views/setup-wizard/good-to-go.php:5
895
+ msgid "Invoices & Packing Slips"
896
+ msgstr ""
897
+
898
+ #: includes/views/setup-wizard/good-to-go.php:6
899
+ msgid "Happy selling!"
900
+ msgstr ""
901
+
902
+ #: includes/views/setup-wizard/logo.php:4
903
+ msgid "Set the header image that will display on your invoice."
904
+ msgstr ""
905
+
906
+ #: includes/views/setup-wizard/paper-format.php:4
907
+ msgid "Select the paper format for your invoice."
908
+ msgstr ""
909
+
910
+ #: includes/views/setup-wizard/shop-name.php:3
911
+ msgid "Enter your shop name"
912
+ msgstr ""
913
+
914
+ #: includes/views/setup-wizard/shop-name.php:4
915
+ msgid ""
916
+ "Lets quickly setup your invoice. Please enter the name and address of your "
917
+ "shop in the fields on the right."
918
+ msgstr ""
919
+
920
+ #: includes/views/setup-wizard/show-action-buttons.php:4
921
+ msgid ""
922
+ "Would you like to display the action buttons in your WooCommerce order list? "
923
+ "The action buttons allow you to manually create a PDF."
924
+ msgstr ""
925
+
926
+ #: includes/views/setup-wizard/show-action-buttons.php:5
927
+ msgid "(You can always change this setting later via the Screen Options menu)"
928
+ msgstr ""
929
+
930
+ #: includes/views/setup-wizard/show-action-buttons.php:18
931
+ msgid "Show action buttons"
932
+ msgstr ""
933
+
934
+ #: includes/views/wcpdf-extensions.php:16
935
+ msgid "Check out these premium extensions!"
936
+ msgstr ""
937
+
938
+ #: includes/views/wcpdf-extensions.php:17
939
+ msgid "click items to read more"
940
+ msgstr ""
941
+
942
+ #: includes/views/wcpdf-extensions.php:22
943
+ msgid ""
944
+ "Premium PDF Invoice bundle: Everything you need for a perfect invoicing "
945
+ "system"
946
+ msgstr ""
947
+
948
+ #: includes/views/wcpdf-extensions.php:24
949
+ msgid ""
950
+ "Supercharge WooCommerce PDF Invoices & Packing Slips with the all our "
951
+ "premium extensions:"
952
+ msgstr ""
953
+
954
+ #: includes/views/wcpdf-extensions.php:25
955
+ msgid "Professional features:"
956
+ msgstr ""
957
+
958
+ #: includes/views/wcpdf-extensions.php:27
959
+ #: includes/views/wcpdf-extensions.php:57
960
+ msgid "Email/print/download <b>PDF Credit Notes & Proforma invoices</b>"
961
+ msgstr ""
962
+
963
+ #: includes/views/wcpdf-extensions.php:28
964
+ #: includes/views/wcpdf-extensions.php:58
965
+ msgid ""
966
+ "Send out a separate <b>notification email</b> with (or without) PDF invoices/"
967
+ "packing slips, for example to a drop-shipper or a supplier."
968
+ msgstr ""
969
+
970
+ #: includes/views/wcpdf-extensions.php:29
971
+ #: includes/views/wcpdf-extensions.php:59
972
+ msgid ""
973
+ "Attach <b>up to 3 static files</b> (for example a terms & conditions "
974
+ "document) to the WooCommerce emails of your choice."
975
+ msgstr ""
976
+
977
+ #: includes/views/wcpdf-extensions.php:30
978
+ #: includes/views/wcpdf-extensions.php:60
979
+ msgid ""
980
+ "Use <b>separate numbering systems</b> and/or format for proforma invoices "
981
+ "and credit notes or utilize the main invoice numbering system"
982
+ msgstr ""
983
+
984
+ #: includes/views/wcpdf-extensions.php:31
985
+ #: includes/views/wcpdf-extensions.php:61
986
+ msgid ""
987
+ "<b>Customize</b> the <b>shipping & billing address</b> format to include "
988
+ "additional custom fields, font sizes etc. without the need to create a "
989
+ "custom template."
990
+ msgstr ""
991
+
992
+ #: includes/views/wcpdf-extensions.php:32
993
+ #: includes/views/wcpdf-extensions.php:62
994
+ msgid "Use the plugin in multilingual <b>WPML</b> setups"
995
+ msgstr ""
996
+
997
+ #: includes/views/wcpdf-extensions.php:34
998
+ #: includes/views/wcpdf-extensions.php:114
999
+ msgid "Advanced, customizable templates"
1000
+ msgstr ""
1001
+
1002
+ #: includes/views/wcpdf-extensions.php:36
1003
+ #: includes/views/wcpdf-extensions.php:117
1004
+ msgid ""
1005
+ "Completely customize the invoice contents (prices, taxes, thumbnails) to "
1006
+ "your needs with a drag & drop customizer"
1007
+ msgstr ""
1008
+
1009
+ #: includes/views/wcpdf-extensions.php:37
1010
+ #: includes/views/wcpdf-extensions.php:118
1011
+ msgid "Two extra stylish premade templates (Modern & Business)"
1012
+ msgstr ""
1013
+
1014
+ #: includes/views/wcpdf-extensions.php:39
1015
+ #: includes/views/wcpdf-extensions.php:63
1016
+ msgid "Upload automatically to dropbox"
1017
+ msgstr ""
1018
+
1019
+ #: includes/views/wcpdf-extensions.php:41
1020
+ msgid ""
1021
+ "This extension conveniently uploads all the invoices (and other pdf "
1022
+ "documents from the professional extension) that are emailed to your "
1023
+ "customers to Dropbox. The best way to keep your invoice administration up to "
1024
+ "date!"
1025
+ msgstr ""
1026
+
1027
+ #: includes/views/wcpdf-extensions.php:44
1028
+ msgid "Get WooCommerce PDF Invoices & Packing Slips Bundle"
1029
+ msgstr ""
1030
+
1031
+ #: includes/views/wcpdf-extensions.php:53
1032
+ msgid "Go Pro: Proforma invoices, credit notes (=refunds) & more!"
1033
+ msgstr ""
1034
+
1035
+ #: includes/views/wcpdf-extensions.php:55
1036
+ msgid ""
1037
+ "Supercharge WooCommerce PDF Invoices & Packing Slips with the following "
1038
+ "features:"
1039
+ msgstr ""
1040
+
1041
+ #: includes/views/wcpdf-extensions.php:65
1042
+ msgid "Get WooCommerce PDF Invoices & Packing Slips Professional!"
1043
+ msgstr ""
1044
+
1045
+ #: includes/views/wcpdf-extensions.php:73
1046
+ msgid "Automatically send payment reminders to your customers"
1047
+ msgstr ""
1048
+
1049
+ #: includes/views/wcpdf-extensions.php:75
1050
+ msgid "WooCommerce Smart Reminder emails"
1051
+ msgstr ""
1052
+
1053
+ #: includes/views/wcpdf-extensions.php:77
1054
+ msgid "<b>Completely automatic</b> scheduled emails"
1055
+ msgstr ""
1056
+
1057
+ #: includes/views/wcpdf-extensions.php:78
1058
+ msgid ""
1059
+ "<b>Rich text editor</b> for the email text, including placeholders for data "
1060
+ "from the order (name, order total, etc)"
1061
+ msgstr ""
1062
+
1063
+ #: includes/views/wcpdf-extensions.php:79
1064
+ msgid ""
1065
+ "Configure the exact requirements for sending an email (time after order, "
1066
+ "order status, payment method)"
1067
+ msgstr ""
1068
+
1069
+ #: includes/views/wcpdf-extensions.php:80
1070
+ msgid ""
1071
+ "Fully <b>WPML Compatible</b> – emails will be automatically sent in the "
1072
+ "order language."
1073
+ msgstr ""
1074
+
1075
+ #: includes/views/wcpdf-extensions.php:81
1076
+ msgid ""
1077
+ "<b>Super versatile!</b> Can be used for any kind of reminder email (review "
1078
+ "reminders, repeat purchases)"
1079
+ msgstr ""
1080
+
1081
+ #: includes/views/wcpdf-extensions.php:82
1082
+ msgid "Integrates seamlessly with the PDF Invoices & Packing Slips plugin"
1083
+ msgstr ""
1084
+
1085
+ #: includes/views/wcpdf-extensions.php:84
1086
+ msgid "Get WooCommerce Smart Reminder Emails"
1087
+ msgstr ""
1088
+
1089
+ #: includes/views/wcpdf-extensions.php:93
1090
+ msgid ""
1091
+ "Automatically send new orders or packing slips to your printer, as soon as "
1092
+ "the customer orders!"
1093
+ msgstr ""
1094
+
1095
+ #: includes/views/wcpdf-extensions.php:99
1096
+ msgid ""
1097
+ "Check out the WooCommerce Automatic Order Printing extension from our "
1098
+ "partners at Simba Hosting"
1099
+ msgstr ""
1100
+
1101
+ #: includes/views/wcpdf-extensions.php:100
1102
+ msgid "WooCommerce Automatic Order Printing"
1103
+ msgstr ""
1104
+
1105
+ #: includes/views/wcpdf-extensions.php:119
1106
+ #, php-format
1107
+ msgid "Check out the Premium PDF Invoice & Packing Slips templates at %s."
1108
+ msgstr ""
1109
+
1110
+ #: includes/views/wcpdf-extensions.php:120
1111
+ #, php-format
1112
+ msgid "For custom templates, contact us at %s."
1113
+ msgstr ""
1114
+
1115
+ #: includes/views/wcpdf-settings-page.php:9
1116
+ msgid "WooCommerce PDF Invoices"
1117
+ msgstr ""
1118
+
1119
+ #: includes/wcpdf-functions.php:208
1120
+ msgid "Error creating PDF, please contact the site owner."
1121
+ msgstr ""
1122
+
1123
+ #: templates/Simple/invoice.php:31 templates/Simple/packing-slip.php:44
1124
+ msgid "Billing Address:"
1125
+ msgstr ""
1126
+
1127
+ #: templates/Simple/invoice.php:44
1128
+ msgid "Ship To:"
1129
+ msgstr ""
1130
+
1131
+ #: templates/Simple/invoice.php:55
1132
+ msgid "Invoice Number:"
1133
+ msgstr ""
1134
+
1135
+ #: templates/Simple/invoice.php:66 templates/Simple/packing-slip.php:54
1136
+ msgid "Order Number:"
1137
+ msgstr ""
1138
+
1139
+ #: templates/Simple/invoice.php:70 templates/Simple/packing-slip.php:58
1140
+ msgid "Order Date:"
1141
+ msgstr ""
1142
+
1143
+ #: templates/Simple/invoice.php:74
1144
+ msgid "Payment Method:"
1145
+ msgstr ""
1146
+
1147
+ #: templates/Simple/invoice.php:88 templates/Simple/packing-slip.php:76
1148
+ msgid "Product"
1149
+ msgstr ""
1150
+
1151
+ #: templates/Simple/invoice.php:89 templates/Simple/packing-slip.php:77
1152
+ msgid "Quantity"
1153
+ msgstr ""
1154
+
1155
+ #: templates/Simple/invoice.php:90
1156
+ msgid "Price"
1157
+ msgstr ""
1158
+
1159
+ #: templates/Simple/invoice.php:97 templates/Simple/packing-slip.php:84
1160
+ msgid "Description"
1161
+ msgstr ""
1162
+
1163
+ #: templates/Simple/invoice.php:102 templates/Simple/packing-slip.php:89
1164
+ msgid "SKU"
1165
+ msgstr ""
1166
+
1167
+ #: templates/Simple/invoice.php:103 templates/Simple/packing-slip.php:90
1168
+ msgid "SKU:"
1169
+ msgstr ""
1170
+
1171
+ #: templates/Simple/invoice.php:104 templates/Simple/packing-slip.php:91
1172
+ msgid "Weight:"
1173
+ msgstr ""
1174
+
1175
+ #: templates/Simple/invoice.php:119 templates/Simple/packing-slip.php:108
1176
+ msgid "Customer Notes"
1177
+ msgstr ""
1178
+
1179
+ #: templates/Simple/packing-slip.php:31
1180
+ msgid "Shipping Address:"
1181
+ msgstr ""
1182
+
1183
+ #: templates/Simple/packing-slip.php:62
1184
+ msgid "Shipping Method:"
1185
+ msgstr ""
1186
+
1187
+ #: woocommerce-pdf-invoices-packingslips.php:249
1188
+ #, php-format
1189
+ msgid ""
1190
+ "WooCommerce PDF Invoices & Packing Slips requires %sWooCommerce%s to be "
1191
+ "installed & activated!"
1192
+ msgstr ""
1193
+
1194
+ #: woocommerce-pdf-invoices-packingslips.php:261
1195
+ msgid ""
1196
+ "WooCommerce PDF Invoices & Packing Slips requires PHP 5.3 or higher (5.6 or "
1197
+ "higher recommended)."
1198
+ msgstr ""
1199
+
1200
+ #: woocommerce-pdf-invoices-packingslips.php:262
1201
+ msgid "How to update your PHP version"
1202
+ msgstr ""
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: woocommerce, pdf, invoices, packing slips, print, delivery notes, invoice,
5
  Requires at least: 3.5
6
  Tested up to: 5.4
7
  Requires PHP: 5.3
8
- Stable tag: 2.5.1
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -102,6 +102,10 @@ There's a setting on the Status tab of the settings page that allows you to togg
102
 
103
  == Changelog ==
104
 
 
 
 
 
105
  = 2.5.1 =
106
  * Fix: Correct integration with permalink settings for `[wcpdf_download_invoice]` shortcode
107
  * Fix: Plugin assets versioning
5
  Requires at least: 3.5
6
  Tested up to: 5.4
7
  Requires PHP: 5.3
8
+ Stable tag: 2.5.2
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
102
 
103
  == Changelog ==
104
 
105
+ = 2.5.2 =
106
+ * Fix: ImageMagick version conflict
107
+ * Translations: Updated POT
108
+
109
  = 2.5.1 =
110
  * Fix: Correct integration with permalink settings for `[wcpdf_download_invoice]` shortcode
111
  * Fix: Plugin assets versioning
vendor/dompdf/dompdf/lib/Cpdf.php CHANGED
@@ -1,5640 +1,5644 @@
1
- <?php
2
- /**
3
- * A PHP class to provide the basic functionality to create a pdf document without
4
- * any requirement for additional modules.
5
- *
6
- * Extended by Orion Richardson to support Unicode / UTF-8 characters using
7
- * TCPDF and others as a guide.
8
- *
9
- * @author Wayne Munro <pdf@ros.co.nz>
10
- * @author Orion Richardson <orionr@yahoo.com>
11
- * @author Helmut Tischer <htischer@weihenstephan.org>
12
- * @author Ryan H. Masten <ryan.masten@gmail.com>
13
- * @author Brian Sweeney <eclecticgeek@gmail.com>
14
- * @author Fabien Ménager <fabien.menager@gmail.com>
15
- * @license Public Domain http://creativecommons.org/licenses/publicdomain/
16
- * @package Cpdf
17
- */
18
- use FontLib\Font;
19
- use FontLib\BinaryStream;
20
-
21
- class Cpdf
22
- {
23
-
24
- /**
25
- * @var integer The current number of pdf objects in the document
26
- */
27
- public $numObj = 0;
28
-
29
- /**
30
- * @var array This array contains all of the pdf objects, ready for final assembly
31
- */
32
- public $objects = array();
33
-
34
- /**
35
- * @var integer The objectId (number within the objects array) of the document catalog
36
- */
37
- public $catalogId;
38
-
39
- /**
40
- * @var array Array carrying information about the fonts that the system currently knows about
41
- * Used to ensure that a font is not loaded twice, among other things
42
- */
43
- public $fonts = array();
44
-
45
- /**
46
- * @var string The default font metrics file to use if no other font has been loaded.
47
- * The path to the directory containing the font metrics should be included
48
- */
49
- public $defaultFont = './fonts/Helvetica.afm';
50
-
51
- /**
52
- * @string A record of the current font
53
- */
54
- public $currentFont = '';
55
-
56
- /**
57
- * @var string The current base font
58
- */
59
- public $currentBaseFont = '';
60
-
61
- /**
62
- * @var integer The number of the current font within the font array
63
- */
64
- public $currentFontNum = 0;
65
-
66
- /**
67
- * @var integer
68
- */
69
- public $currentNode;
70
-
71
- /**
72
- * @var integer Object number of the current page
73
- */
74
- public $currentPage;
75
-
76
- /**
77
- * @var integer Object number of the currently active contents block
78
- */
79
- public $currentContents;
80
-
81
- /**
82
- * @var integer Number of fonts within the system
83
- */
84
- public $numFonts = 0;
85
-
86
- /**
87
- * @var integer Number of graphic state resources used
88
- */
89
- private $numStates = 0;
90
-
91
- /**
92
- * @var array Number of graphic state resources used
93
- */
94
- private $gstates = array();
95
-
96
- /**
97
- * @var array Current color for fill operations, defaults to inactive value,
98
- * all three components should be between 0 and 1 inclusive when active
99
- */
100
- public $currentColor = null;
101
-
102
- /**
103
- * @var array Current color for stroke operations (lines etc.)
104
- */
105
- public $currentStrokeColor = null;
106
-
107
- /**
108
- * @var string Fill rule (nonzero or evenodd)
109
- */
110
- public $fillRule = "nonzero";
111
-
112
- /**
113
- * @var string Current style that lines are drawn in
114
- */
115
- public $currentLineStyle = '';
116
-
117
- /**
118
- * @var array Current line transparency (partial graphics state)
119
- */
120
- public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
121
-
122
- /**
123
- * array Current fill transparency (partial graphics state)
124
- */
125
- public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
126
-
127
- /**
128
- * @var array An array which is used to save the state of the document, mainly the colors and styles
129
- * it is used to temporarily change to another state, then change back to what it was before
130
- */
131
- public $stateStack = array();
132
-
133
- /**
134
- * @var integer Number of elements within the state stack
135
- */
136
- public $nStateStack = 0;
137
-
138
- /**
139
- * @var integer Number of page objects within the document
140
- */
141
- public $numPages = 0;
142
-
143
- /**
144
- * @var array Object Id storage stack
145
- */
146
- public $stack = array();
147
-
148
- /**
149
- * @var integer Number of elements within the object Id storage stack
150
- */
151
- public $nStack = 0;
152
-
153
- /**
154
- * an array which contains information about the objects which are not firmly attached to pages
155
- * these have been added with the addObject function
156
- */
157
- public $looseObjects = array();
158
-
159
- /**
160
- * array contains information about how the loose objects are to be added to the document
161
- */
162
- public $addLooseObjects = array();
163
-
164
- /**
165
- * @var integer The objectId of the information object for the document
166
- * this contains authorship, title etc.
167
- */
168
- public $infoObject = 0;
169
-
170
- /**
171
- * @var integer Number of images being tracked within the document
172
- */
173
- public $numImages = 0;
174
-
175
- /**
176
- * @var array An array containing options about the document
177
- * it defaults to turning on the compression of the objects
178
- */
179
- public $options = array('compression' => true);
180
-
181
- /**
182
- * @var integer The objectId of the first page of the document
183
- */
184
- public $firstPageId;
185
-
186
- /**
187
- * @var integer The object Id of the procset object
188
- */
189
- public $procsetObjectId;
190
-
191
- /**
192
- * @var array Store the information about the relationship between font families
193
- * this used so that the code knows which font is the bold version of another font, etc.
194
- * the value of this array is initialised in the constructor function.
195
- */
196
- public $fontFamilies = array();
197
-
198
- /**
199
- * @var string Folder for php serialized formats of font metrics files.
200
- * If empty string, use same folder as original metrics files.
201
- * This can be passed in from class creator.
202
- * If this folder does not exist or is not writable, Cpdf will be **much** slower.
203
- * Because of potential trouble with php safe mode, folder cannot be created at runtime.
204
- */
205
- public $fontcache = '';
206
-
207
- /**
208
- * @var integer The version of the font metrics cache file.
209
- * This value must be manually incremented whenever the internal font data structure is modified.
210
- */
211
- public $fontcacheVersion = 6;
212
-
213
- /**
214
- * @var string Temporary folder.
215
- * If empty string, will attempt system tmp folder.
216
- * This can be passed in from class creator.
217
- */
218
- public $tmp = '';
219
-
220
- /**
221
- * @var string Track if the current font is bolded or italicised
222
- */
223
- public $currentTextState = '';
224
-
225
- /**
226
- * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
227
- */
228
- public $messages = '';
229
-
230
- /**
231
- * @var string The encryption array for the document encryption is stored here
232
- */
233
- public $arc4 = '';
234
-
235
- /**
236
- * @var integer The object Id of the encryption information
237
- */
238
- public $arc4_objnum = 0;
239
-
240
- /**
241
- * @var string The file identifier, used to uniquely identify a pdf document
242
- */
243
- public $fileIdentifier = '';
244
-
245
- /**
246
- * @var boolean A flag to say if a document is to be encrypted or not
247
- */
248
- public $encrypted = false;
249
-
250
- /**
251
- * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
252
- */
253
- public $encryptionKey = '';
254
-
255
- /**
256
- * @var array Array which forms a stack to keep track of nested callback functions
257
- */
258
- public $callback = array();
259
-
260
- /**
261
- * @var integer The number of callback functions in the callback array
262
- */
263
- public $nCallback = 0;
264
-
265
- /**
266
- * @var array Store label->id pairs for named destinations, these will be used to replace internal links
267
- * done this way so that destinations can be defined after the location that links to them
268
- */
269
- public $destinations = array();
270
-
271
- /**
272
- * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
273
- * publiciables within the class, so that the user can rollback at will (from each 'start' command)
274
- * note that this includes the objects array, so these can be large.
275
- */
276
- public $checkpoint = '';
277
-
278
- /**
279
- * @var array Table of Image origin filenames and image labels which were already added with o_image().
280
- * Allows to merge identical images
281
- */
282
- public $imagelist = array();
283
-
284
- /**
285
- * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
286
- */
287
- public $isUnicode = false;
288
-
289
- /**
290
- * @var string the JavaScript code of the document
291
- */
292
- public $javascript = '';
293
-
294
- /**
295
- * @var boolean whether the compression is possible
296
- */
297
- protected $compressionReady = false;
298
-
299
- /**
300
- * @var array Current page size
301
- */
302
- protected $currentPageSize = array("width" => 0, "height" => 0);
303
-
304
- /**
305
- * @var array All the chars that will be required in the font subsets
306
- */
307
- protected $stringSubsets = array();
308
-
309
- /**
310
- * @var string The target internal encoding
311
- */
312
- static protected $targetEncoding = 'Windows-1252';
313
-
314
- /**
315
- * @var array The list of the core fonts
316
- */
317
- static protected $coreFonts = array(
318
- 'courier',
319
- 'courier-bold',
320
- 'courier-oblique',
321
- 'courier-boldoblique',
322
- 'helvetica',
323
- 'helvetica-bold',
324
- 'helvetica-oblique',
325
- 'helvetica-boldoblique',
326
- 'times-roman',
327
- 'times-bold',
328
- 'times-italic',
329
- 'times-bolditalic',
330
- 'symbol',
331
- 'zapfdingbats'
332
- );
333
-
334
- /**
335
- * Class constructor
336
- * This will start a new document
337
- *
338
- * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
339
- * @param boolean $isUnicode Whether text will be treated as Unicode or not.
340
- * @param string $fontcache The font cache folder
341
- * @param string $tmp The temporary folder
342
- */
343
- function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '')
344
- {
345
- $this->isUnicode = $isUnicode;
346
- $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
347
- $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
348
- $this->newDocument($pageSize);
349
-
350
- $this->compressionReady = function_exists('gzcompress');
351
-
352
- if (in_array('Windows-1252', mb_list_encodings())) {
353
- self::$targetEncoding = 'Windows-1252';
354
- }
355
-
356
- // also initialize the font families that are known about already
357
- $this->setFontFamily('init');
358
- }
359
-
360
- /**
361
- * Document object methods (internal use only)
362
- *
363
- * There is about one object method for each type of object in the pdf document
364
- * Each function has the same call list ($id,$action,$options).
365
- * $id = the object ID of the object, or what it is to be if it is being created
366
- * $action = a string specifying the action to be performed, though ALL must support:
367
- * 'new' - create the object with the id $id
368
- * 'out' - produce the output for the pdf object
369
- * $options = optional, a string or array containing the various parameters for the object
370
- *
371
- * These, in conjunction with the output function are the ONLY way for output to be produced
372
- * within the pdf 'file'.
373
- */
374
-
375
- /**
376
- * Destination object, used to specify the location for the user to jump to, presently on opening
377
- *
378
- * @param $id
379
- * @param $action
380
- * @param string $options
381
- * @return string|null
382
- */
383
- protected function o_destination($id, $action, $options = '')
384
- {
385
- switch ($action) {
386
- case 'new':
387
- $this->objects[$id] = array('t' => 'destination', 'info' => array());
388
- $tmp = '';
389
- switch ($options['type']) {
390
- case 'XYZ':
391
- /** @noinspection PhpMissingBreakStatementInspection */
392
- case 'FitR':
393
- $tmp = ' ' . $options['p3'] . $tmp;
394
- case 'FitH':
395
- case 'FitV':
396
- case 'FitBH':
397
- /** @noinspection PhpMissingBreakStatementInspection */
398
- case 'FitBV':
399
- $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
400
- case 'Fit':
401
- case 'FitB':
402
- $tmp = $options['type'] . $tmp;
403
- $this->objects[$id]['info']['string'] = $tmp;
404
- $this->objects[$id]['info']['page'] = $options['page'];
405
- }
406
- break;
407
-
408
- case 'out':
409
- $o = &$this->objects[$id];
410
-
411
- $tmp = $o['info'];
412
- $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
413
-
414
- return $res;
415
- }
416
-
417
- return null;
418
- }
419
-
420
- /**
421
- * set the viewer preferences
422
- *
423
- * @param $id
424
- * @param $action
425
- * @param string|array $options
426
- * @return string|null
427
- */
428
- protected function o_viewerPreferences($id, $action, $options = '')
429
- {
430
- switch ($action) {
431
- case 'new':
432
- $this->objects[$id] = array('t' => 'viewerPreferences', 'info' => array());
433
- break;
434
-
435
- case 'add':
436
- $o = &$this->objects[$id];
437
-
438
- foreach ($options as $k => $v) {
439
- switch ($k) {
440
- // Boolean keys
441
- case 'HideToolbar':
442
- case 'HideMenubar':
443
- case 'HideWindowUI':
444
- case 'FitWindow':
445
- case 'CenterWindow':
446
- case 'DisplayDocTitle':
447
- case 'PickTrayByPDFSize':
448
- $o['info'][$k] = (bool)$v;
449
- break;
450
-
451
- // Integer keys
452
- case 'NumCopies':
453
- $o['info'][$k] = (int)$v;
454
- break;
455
-
456
- // Name keys
457
- case 'ViewArea':
458
- case 'ViewClip':
459
- case 'PrintClip':
460
- case 'PrintArea':
461
- $o['info'][$k] = (string)$v;
462
- break;
463
-
464
- // Named with limited valid values
465
- case 'NonFullScreenPageMode':
466
- if (!in_array($v, array('UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'))) {
467
- break;
468
- }
469
- $o['info'][$k] = $v;
470
- break;
471
-
472
- case 'Direction':
473
- if (!in_array($v, array('L2R', 'R2L'))) {
474
- break;
475
- }
476
- $o['info'][$k] = $v;
477
- break;
478
-
479
- case 'PrintScaling':
480
- if (!in_array($v, array('None', 'AppDefault'))) {
481
- break;
482
- }
483
- $o['info'][$k] = $v;
484
- break;
485
-
486
- case 'Duplex':
487
- if (!in_array($v, array('None', 'AppDefault'))) {
488
- break;
489
- }
490
- $o['info'][$k] = $v;
491
- break;
492
-
493
- // Integer array
494
- case 'PrintPageRange':
495
- // Cast to integer array
496
- foreach ($v as $vK => $vV) {
497
- $v[$vK] = (int)$vV;
498
- }
499
- $o['info'][$k] = array_values($v);
500
- break;
501
- }
502
- }
503
- break;
504
-
505
- case 'out':
506
- $o = &$this->objects[$id];
507
- $res = "\n$id 0 obj\n<< ";
508
-
509
- foreach ($o['info'] as $k => $v) {
510
- if (is_string($v)) {
511
- $v = '/' . $v;
512
- } elseif (is_int($v)) {
513
- $v = (string) $v;
514
- } elseif (is_bool($v)) {
515
- $v = ($v ? 'true' : 'false');
516
- } elseif (is_array($v)) {
517
- $v = '[' . implode(' ', $v) . ']';
518
- }
519
- $res .= "\n/$k $v";
520
- }
521
- $res .= "\n>>\n";
522
-
523
- return $res;
524
- }
525
-
526
- return null;
527
- }
528
-
529
- /**
530
- * define the document catalog, the overall controller for the document
531
- *
532
- * @param $id
533
- * @param $action
534
- * @param string|array $options
535
- * @return string|null
536
- */
537
- protected function o_catalog($id, $action, $options = '')
538
- {
539
- if ($action !== 'new') {
540
- $o = &$this->objects[$id];
541
- }
542
-
543
- switch ($action) {
544
- case 'new':
545
- $this->objects[$id] = array('t' => 'catalog', 'info' => array());
546
- $this->catalogId = $id;
547
- break;
548
-
549
- case 'outlines':
550
- case 'pages':
551
- case 'openHere':
552
- case 'javascript':
553
- $o['info'][$action] = $options;
554
- break;
555
-
556
- case 'viewerPreferences':
557
- if (!isset($o['info']['viewerPreferences'])) {
558
- $this->numObj++;
559
- $this->o_viewerPreferences($this->numObj, 'new');
560
- $o['info']['viewerPreferences'] = $this->numObj;
561
- }
562
-
563
- $vp = $o['info']['viewerPreferences'];
564
- $this->o_viewerPreferences($vp, 'add', $options);
565
-
566
- break;
567
-
568
- case 'out':
569
- $res = "\n$id 0 obj\n<< /Type /Catalog";
570
-
571
- foreach ($o['info'] as $k => $v) {
572
- switch ($k) {
573
- case 'outlines':
574
- $res .= "\n/Outlines $v 0 R";
575
- break;
576
-
577
- case 'pages':
578
- $res .= "\n/Pages $v 0 R";
579
- break;
580
-
581
- case 'viewerPreferences':
582
- $res .= "\n/ViewerPreferences $v 0 R";
583
- break;
584
-
585
- case 'openHere':
586
- $res .= "\n/OpenAction $v 0 R";
587
- break;
588
-
589
- case 'javascript':
590
- $res .= "\n/Names <</JavaScript $v 0 R>>";
591
- break;
592
- }
593
- }
594
-
595
- $res .= " >>\nendobj";
596
-
597
- return $res;
598
- }
599
-
600
- return null;
601
- }
602
-
603
- /**
604
- * object which is a parent to the pages in the document
605
- *
606
- * @param $id
607
- * @param $action
608
- * @param string $options
609
- * @return string|null
610
- */
611
- protected function o_pages($id, $action, $options = '')
612
- {
613
- if ($action !== 'new') {
614
- $o = &$this->objects[$id];
615
- }
616
-
617
- switch ($action) {
618
- case 'new':
619
- $this->objects[$id] = array('t' => 'pages', 'info' => array());
620
- $this->o_catalog($this->catalogId, 'pages', $id);
621
- break;
622
-
623
- case 'page':
624
- if (!is_array($options)) {
625
- // then it will just be the id of the new page
626
- $o['info']['pages'][] = $options;
627
- } else {
628
- // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
629
- // and pos is either 'before' or 'after', saying where this page will fit.
630
- if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
631
- $i = array_search($options['rid'], $o['info']['pages']);
632
- if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
633
-
634
- // then there is a match
635
- // make a space
636
- switch ($options['pos']) {
637
- case 'before':
638
- $k = $i;
639
- break;
640
-
641
- case 'after':
642
- $k = $i + 1;
643
- break;
644
-
645
- default:
646
- $k = -1;
647
- break;
648
- }
649
-
650
- if ($k >= 0) {
651
- for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
652
- $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
653
- }
654
-
655
- $o['info']['pages'][$k] = $options['id'];
656
- }
657
- }
658
- }
659
- }
660
- break;
661
-
662
- case 'procset':
663
- $o['info']['procset'] = $options;
664
- break;
665
-
666
- case 'mediaBox':
667
- $o['info']['mediaBox'] = $options;
668
- // which should be an array of 4 numbers
669
- $this->currentPageSize = array('width' => $options[2], 'height' => $options[3]);
670
- break;
671
-
672
- case 'font':
673
- $o['info']['fonts'][] = array('objNum' => $options['objNum'], 'fontNum' => $options['fontNum']);
674
- break;
675
-
676
- case 'extGState':
677
- $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']);
678
- break;
679
-
680
- case 'xObject':
681
- $o['info']['xObjects'][] = array('objNum' => $options['objNum'], 'label' => $options['label']);
682
- break;
683
-
684
- case 'out':
685
- if (count($o['info']['pages'])) {
686
- $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
687
- foreach ($o['info']['pages'] as $v) {
688
- $res .= "$v 0 R\n";
689
- }
690
-
691
- $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
692
-
693
- if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
694
- isset($o['info']['procset']) ||
695
- (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
696
- ) {
697
- $res .= "\n/Resources <<";
698
-
699
- if (isset($o['info']['procset'])) {
700
- $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
701
- }
702
-
703
- if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
704
- $res .= "\n/Font << ";
705
- foreach ($o['info']['fonts'] as $finfo) {
706
- $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
707
- }
708
- $res .= "\n>>";
709
- }
710
-
711
- if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
712
- $res .= "\n/XObject << ";
713
- foreach ($o['info']['xObjects'] as $finfo) {
714
- $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
715
- }
716
- $res .= "\n>>";
717
- }
718
-
719
- if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
720
- $res .= "\n/ExtGState << ";
721
- foreach ($o['info']['extGStates'] as $gstate) {
722
- $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
723
- }
724
- $res .= "\n>>";
725
- }
726
-
727
- $res .= "\n>>";
728
- if (isset($o['info']['mediaBox'])) {
729
- $tmp = $o['info']['mediaBox'];
730
- $res .= "\n/MediaBox [" . sprintf(
731
- '%.3F %.3F %.3F %.3F',
732
- $tmp[0],
733
- $tmp[1],
734
- $tmp[2],
735
- $tmp[3]
736
- ) . ']';
737
- }
738
- }
739
-
740
- $res .= "\n >>\nendobj";
741
- } else {
742
- $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
743
- }
744
-
745
- return $res;
746
- }
747
-
748
- return null;
749
- }
750
-
751
- /**
752
- * define the outlines in the doc, empty for now
753
- *
754
- * @param $id
755
- * @param $action
756
- * @param string $options
757
- * @return string|null
758
- */
759
- protected function o_outlines($id, $action, $options = '')
760
- {
761
- if ($action !== 'new') {
762
- $o = &$this->objects[$id];
763
- }
764
-
765
- switch ($action) {
766
- case 'new':
767
- $this->objects[$id] = array('t' => 'outlines', 'info' => array('outlines' => array()));
768
- $this->o_catalog($this->catalogId, 'outlines', $id);
769
- break;
770
-
771
- case 'outline':
772
- $o['info']['outlines'][] = $options;
773
- break;
774
-
775
- case 'out':
776
- if (count($o['info']['outlines'])) {
777
- $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
778
- foreach ($o['info']['outlines'] as $v) {
779
- $res .= "$v 0 R ";
780
- }
781
-
782
- $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
783
- } else {
784
- $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
785
- }
786
-
787
- return $res;
788
- }
789
-
790
- return null;
791
- }
792
-
793
- /**
794
- * an object to hold the font description
795
- *
796
- * @param $id
797
- * @param $action
798
- * @param string|array $options
799
- * @return string|null
800
- */
801
- protected function o_font($id, $action, $options = '')
802
- {
803
- if ($action !== 'new') {
804
- $o = &$this->objects[$id];
805
- }
806
-
807
- switch ($action) {
808
- case 'new':
809
- $this->objects[$id] = array(
810
- 't' => 'font',
811
- 'info' => array(
812
- 'name' => $options['name'],
813
- 'fontFileName' => $options['fontFileName'],
814
- 'SubType' => 'Type1'
815
- )
816
- );
817
- $fontNum = $this->numFonts;
818
- $this->objects[$id]['info']['fontNum'] = $fontNum;
819
-
820
- // deal with the encoding and the differences
821
- if (isset($options['differences'])) {
822
- // then we'll need an encoding dictionary
823
- $this->numObj++;
824
- $this->o_fontEncoding($this->numObj, 'new', $options);
825
- $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
826
- } else {
827
- if (isset($options['encoding'])) {
828
- // we can specify encoding here
829
- switch ($options['encoding']) {
830
- case 'WinAnsiEncoding':
831
- case 'MacRomanEncoding':
832
- case 'MacExpertEncoding':
833
- $this->objects[$id]['info']['encoding'] = $options['encoding'];
834
- break;
835
-
836
- case 'none':
837
- break;
838
-
839
- default:
840
- $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
841
- break;
842
- }
843
- } else {
844
- $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
845
- }
846
- }
847
-
848
- if ($this->fonts[$options['fontFileName']]['isUnicode']) {
849
- // For Unicode fonts, we need to incorporate font data into
850
- // sub-sections that are linked from the primary font section.
851
- // Look at o_fontGIDtoCID and o_fontDescendentCID functions
852
- // for more information.
853
- //
854
- // All of this code is adapted from the excellent changes made to
855
- // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
856
-
857
- $toUnicodeId = ++$this->numObj;
858
- $this->o_toUnicode($toUnicodeId, 'new');
859
- $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
860
-
861
- $cidFontId = ++$this->numObj;
862
- $this->o_fontDescendentCID($cidFontId, 'new', $options);
863
- $this->objects[$id]['info']['cidFont'] = $cidFontId;
864
- }
865
-
866
- // also tell the pages node about the new font
867
- $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
868
- break;
869
-
870
- case 'add':
871
- foreach ($options as $k => $v) {
872
- switch ($k) {
873
- case 'BaseFont':
874
- $o['info']['name'] = $v;
875
- break;
876
- case 'FirstChar':
877
- case 'LastChar':
878
- case 'Widths':
879
- case 'FontDescriptor':
880
- case 'SubType':
881
- $this->addMessage('o_font ' . $k . " : " . $v);
882
- $o['info'][$k] = $v;
883
- break;
884
- }
885
- }
886
-
887
- // pass values down to descendent font
888
- if (isset($o['info']['cidFont'])) {
889
- $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
890
- }
891
- break;
892
-
893
- case 'out':
894
- if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
895
- // For Unicode fonts, we need to incorporate font data into
896
- // sub-sections that are linked from the primary font section.
897
- // Look at o_fontGIDtoCID and o_fontDescendentCID functions
898
- // for more information.
899
- //
900
- // All of this code is adapted from the excellent changes made to
901
- // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
902
-
903
- $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
904
- $res .= "/BaseFont /" . $o['info']['name'] . "\n";
905
-
906
- // The horizontal identity mapping for 2-byte CIDs; may be used
907
- // with CIDFonts using any Registry, Ordering, and Supplement values.
908
- $res .= "/Encoding /Identity-H\n";
909
- $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
910
- $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
911
- $res .= ">>\n";
912
- $res .= "endobj";
913
- } else {
914
- $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
915
- $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
916
- $res .= "/BaseFont /" . $o['info']['name'] . "\n";
917
-
918
- if (isset($o['info']['encodingDictionary'])) {
919
- // then place a reference to the dictionary
920
- $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
921
- } else {
922
- if (isset($o['info']['encoding'])) {
923
- // use the specified encoding
924
- $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
925
- }
926
- }
927
-
928
- if (isset($o['info']['FirstChar'])) {
929
- $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
930
- }
931
-
932
- if (isset($o['info']['LastChar'])) {
933
- $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
934
- }
935
-
936
- if (isset($o['info']['Widths'])) {
937
- $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
938
- }
939
-
940
- if (isset($o['info']['FontDescriptor'])) {
941
- $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
942
- }
943
-
944
- $res .= ">>\n";
945
- $res .= "endobj";
946
- }
947
-
948
- return $res;
949
- }
950
-
951
- return null;
952
- }
953
-
954
- /**
955
- * A toUnicode section, needed for unicode fonts
956
- *
957
- * @param $id
958
- * @param $action
959
- * @return null|string
960
- */
961
- protected function o_toUnicode($id, $action)
962
- {
963
- switch ($action) {
964
- case 'new':
965
- $this->objects[$id] = array(
966
- 't' => 'toUnicode'
967
- );
968
- break;
969
- case 'add':
970
- break;
971
- case 'out':
972
- $ordering = '(UCS)';
973
- $registry = '(Adobe)';
974
-
975
- if ($this->encrypted) {
976
- $this->encryptInit($id);
977
- $ordering = $this->ARC4($ordering);
978
- $registry = $this->ARC4($registry);
979
- }
980
-
981
- $stream = <<<EOT
982
- /CIDInit /ProcSet findresource begin
983
- 12 dict begin
984
- begincmap
985
- /CIDSystemInfo
986
- <</Registry $registry
987
- /Ordering $ordering
988
- /Supplement 0
989
- >> def
990
- /CMapName /Adobe-Identity-UCS def
991
- /CMapType 2 def
992
- 1 begincodespacerange
993
- <0000> <FFFF>
994
- endcodespacerange
995
- 1 beginbfrange
996
- <0000> <FFFF> <0000>
997
- endbfrange
998
- endcmap
999
- CMapName currentdict /CMap defineresource pop
1000
- end
1001
- end
1002
- EOT;
1003
-
1004
- $res = "\n$id 0 obj\n";
1005
- $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1006
- $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
1007
-
1008
- return $res;
1009
- }
1010
-
1011
- return null;
1012
- }
1013
-
1014
- /**
1015
- * a font descriptor, needed for including additional fonts
1016
- *
1017
- * @param $id
1018
- * @param $action
1019
- * @param string $options
1020
- * @return null|string
1021
- */
1022
- protected function o_fontDescriptor($id, $action, $options = '')
1023
- {
1024
- if ($action !== 'new') {
1025
- $o = &$this->objects[$id];
1026
- }
1027
-
1028
- switch ($action) {
1029
- case 'new':
1030
- $this->objects[$id] = array('t' => 'fontDescriptor', 'info' => $options);
1031
- break;
1032
-
1033
- case 'out':
1034
- $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1035
- foreach ($o['info'] as $label => $value) {
1036
- switch ($label) {
1037
- case 'Ascent':
1038
- case 'CapHeight':
1039
- case 'Descent':
1040
- case 'Flags':
1041
- case 'ItalicAngle':
1042
- case 'StemV':
1043
- case 'AvgWidth':
1044
- case 'Leading':
1045
- case 'MaxWidth':
1046
- case 'MissingWidth':
1047
- case 'StemH':
1048
- case 'XHeight':
1049
- case 'CharSet':
1050
- if (mb_strlen($value, '8bit')) {
1051
- $res .= "/$label $value\n";
1052
- }
1053
-
1054
- break;
1055
- case 'FontFile':
1056
- case 'FontFile2':
1057
- case 'FontFile3':
1058
- $res .= "/$label $value 0 R\n";
1059
- break;
1060
-
1061
- case 'FontBBox':
1062
- $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1063
- break;
1064
-
1065
- case 'FontName':
1066
- $res .= "/$label /$value\n";
1067
- break;
1068
- }
1069
- }
1070
-
1071
- $res .= ">>\nendobj";
1072
-
1073
- return $res;
1074
- }
1075
-
1076
- return null;
1077
- }
1078
-
1079
- /**
1080
- * the font encoding
1081
- *
1082
- * @param $id
1083
- * @param $action
1084
- * @param string $options
1085
- * @return null|string
1086
- */
1087
- protected function o_fontEncoding($id, $action, $options = '')
1088
- {
1089
- if ($action !== 'new') {
1090
- $o = &$this->objects[$id];
1091
- }
1092
-
1093
- switch ($action) {
1094
- case 'new':
1095
- // the options array should contain 'differences' and maybe 'encoding'
1096
- $this->objects[$id] = array('t' => 'fontEncoding', 'info' => $options);
1097
- break;
1098
-
1099
- case 'out':
1100
- $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1101
- if (!isset($o['info']['encoding'])) {
1102
- $o['info']['encoding'] = 'WinAnsiEncoding';
1103
- }
1104
-
1105
- if ($o['info']['encoding'] !== 'none') {
1106
- $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1107
- }
1108
-
1109
- $res .= "/Differences \n[";
1110
-
1111
- $onum = -100;
1112
-
1113
- foreach ($o['info']['differences'] as $num => $label) {
1114
- if ($num != $onum + 1) {
1115
- // we cannot make use of consecutive numbering
1116
- $res .= "\n$num /$label";
1117
- } else {
1118
- $res .= " /$label";
1119
- }
1120
-
1121
- $onum = $num;
1122
- }
1123
-
1124
- $res .= "\n]\n>>\nendobj";
1125
-
1126
- return $res;
1127
- }
1128
-
1129
- return null;
1130
- }
1131
-
1132
- /**
1133
- * a descendent cid font, needed for unicode fonts
1134
- *
1135
- * @param $id
1136
- * @param $action
1137
- * @param string|array $options
1138
- * @return null|string
1139
- */
1140
- protected function o_fontDescendentCID($id, $action, $options = '')
1141
- {
1142
- if ($action !== 'new') {
1143
- $o = &$this->objects[$id];
1144
- }
1145
-
1146
- switch ($action) {
1147
- case 'new':
1148
- $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options);
1149
-
1150
- // we need a CID system info section
1151
- $cidSystemInfoId = ++$this->numObj;
1152
- $this->o_cidSystemInfo($cidSystemInfoId, 'new');
1153
- $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1154
-
1155
- // and a CID to GID map
1156
- $cidToGidMapId = ++$this->numObj;
1157
- $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1158
- $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1159
- break;
1160
-
1161
- case 'add':
1162
- foreach ($options as $k => $v) {
1163
- switch ($k) {
1164
- case 'BaseFont':
1165
- $o['info']['name'] = $v;
1166
- break;
1167
-
1168
- case 'FirstChar':
1169
- case 'LastChar':
1170
- case 'MissingWidth':
1171
- case 'FontDescriptor':
1172
- case 'SubType':
1173
- $this->addMessage("o_fontDescendentCID $k : $v");
1174
- $o['info'][$k] = $v;
1175
- break;
1176
- }
1177
- }
1178
-
1179
- // pass values down to cid to gid map
1180
- $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1181
- break;
1182
-
1183
- case 'out':
1184
- $res = "\n$id 0 obj\n";
1185
- $res .= "<</Type /Font\n";
1186
- $res .= "/Subtype /CIDFontType2\n";
1187
- $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1188
- $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1189
- // if (isset($o['info']['FirstChar'])) {
1190
- // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1191
- // }
1192
-
1193
- // if (isset($o['info']['LastChar'])) {
1194
- // $res.= "/LastChar ".$o['info']['LastChar']."\n";
1195
- // }
1196
- if (isset($o['info']['FontDescriptor'])) {
1197
- $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1198
- }
1199
-
1200
- if (isset($o['info']['MissingWidth'])) {
1201
- $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1202
- }
1203
-
1204
- if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1205
- $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1206
- $w = '';
1207
- foreach ($cid_widths as $cid => $width) {
1208
- $w .= "$cid [$width] ";
1209
- }
1210
- $res .= "/W [$w]\n";
1211
- }
1212
-
1213
- $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1214
- $res .= ">>\n";
1215
- $res .= "endobj";
1216
-
1217
- return $res;
1218
- }
1219
-
1220
- return null;
1221
- }
1222
-
1223
- /**
1224
- * CID system info section, needed for unicode fonts
1225
- *
1226
- * @param $id
1227
- * @param $action
1228
- * @return null|string
1229
- */
1230
- protected function o_cidSystemInfo($id, $action)
1231
- {
1232
- switch ($action) {
1233
- case 'new':
1234
- $this->objects[$id] = array(
1235
- 't' => 'cidSystemInfo'
1236
- );
1237
- break;
1238
- case 'add':
1239
- break;
1240
- case 'out':
1241
- $ordering = '(UCS)';
1242
- $registry = '(Adobe)';
1243
-
1244
- if ($this->encrypted) {
1245
- $this->encryptInit($id);
1246
- $ordering = $this->ARC4($ordering);
1247
- $registry = $this->ARC4($registry);
1248
- }
1249
-
1250
-
1251
- $res = "\n$id 0 obj\n";
1252
-
1253
- $res .= '<</Registry ' . $registry . "\n"; // A string identifying an issuer of character collections
1254
- $res .= '/Ordering ' . $ordering . "\n"; // A string that uniquely names a character collection issued by a specific registry
1255
- $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1256
- $res .= ">>";
1257
-
1258
- $res .= "\nendobj";;
1259
-
1260
- return $res;
1261
- }
1262
-
1263
- return null;
1264
- }
1265
-
1266
- /**
1267
- * a font glyph to character map, needed for unicode fonts
1268
- *
1269
- * @param $id
1270
- * @param $action
1271
- * @param string $options
1272
- * @return null|string
1273
- */
1274
- protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1275
- {
1276
- if ($action !== 'new') {
1277
- $o = &$this->objects[$id];
1278
- }
1279
-
1280
- switch ($action) {
1281
- case 'new':
1282
- $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options);
1283
- break;
1284
-
1285
- case 'out':
1286
- $res = "\n$id 0 obj\n";
1287
- $fontFileName = $o['info']['fontFileName'];
1288
- $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1289
-
1290
- $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1291
- $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1292
-
1293
- if (!$compressed && isset($o['raw'])) {
1294
- $res .= $tmp;
1295
- } else {
1296
- $res .= "<<";
1297
-
1298
- if (!$compressed && $this->compressionReady && $this->options['compression']) {
1299
- // then implement ZLIB based compression on this content stream
1300
- $compressed = true;
1301
- $tmp = gzcompress($tmp, 6);
1302
- }
1303
- if ($compressed) {
1304
- $res .= "\n/Filter /FlateDecode";
1305
- }
1306
-
1307
- if ($this->encrypted) {
1308
- $this->encryptInit($id);
1309
- $tmp = $this->ARC4($tmp);
1310
- }
1311
-
1312
- $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1313
- }
1314
-
1315
- $res .= "\nendobj";
1316
-
1317
- return $res;
1318
- }
1319
-
1320
- return null;
1321
- }
1322
-
1323
- /**
1324
- * the document procset, solves some problems with printing to old PS printers
1325
- *
1326
- * @param $id
1327
- * @param $action
1328
- * @param string $options
1329
- * @return null|string
1330
- */
1331
- protected function o_procset($id, $action, $options = '')
1332
- {
1333
- if ($action !== 'new') {
1334
- $o = &$this->objects[$id];
1335
- }
1336
-
1337
- switch ($action) {
1338
- case 'new':
1339
- $this->objects[$id] = array('t' => 'procset', 'info' => array('PDF' => 1, 'Text' => 1));
1340
- $this->o_pages($this->currentNode, 'procset', $id);
1341
- $this->procsetObjectId = $id;
1342
- break;
1343
-
1344
- case 'add':
1345
- // this is to add new items to the procset list, despite the fact that this is considered
1346
- // obsolete, the items are required for printing to some postscript printers
1347
- switch ($options) {
1348
- case 'ImageB':
1349
- case 'ImageC':
1350
- case 'ImageI':
1351
- $o['info'][$options] = 1;
1352
- break;
1353
- }
1354
- break;
1355
-
1356
- case 'out':
1357
- $res = "\n$id 0 obj\n[";
1358
- foreach ($o['info'] as $label => $val) {
1359
- $res .= "/$label ";
1360
- }
1361
- $res .= "]\nendobj";
1362
-
1363
- return $res;
1364
- }
1365
-
1366
- return null;
1367
- }
1368
-
1369
- /**
1370
- * define the document information
1371
- *
1372
- * @param $id
1373
- * @param $action
1374
- * @param string $options
1375
- * @return null|string
1376
- */
1377
- protected function o_info($id, $action, $options = '')
1378
- {
1379
- switch ($action) {
1380
- case 'new':
1381
- $this->infoObject = $id;
1382
- $date = 'D:' . @date('Ymd');
1383
- $this->objects[$id] = array(
1384
- 't' => 'info',
1385
- 'info' => array(
1386
- 'Producer' => 'CPDF (dompdf)',
1387
- 'CreationDate' => $date
1388
- )
1389
- );
1390
- break;
1391
- case 'Title':
1392
- case 'Author':
1393
- case 'Subject':
1394
- case 'Keywords':
1395
- case 'Creator':
1396
- case 'Producer':
1397
- case 'CreationDate':
1398
- case 'ModDate':
1399
- case 'Trapped':
1400
- $this->objects[$id]['info'][$action] = $options;
1401
- break;
1402
-
1403
- case 'out':
1404
- $encrypted = $this->encrypted;
1405
- if ($encrypted) {
1406
- $this->encryptInit($id);
1407
- }
1408
-
1409
- $res = "\n$id 0 obj\n<<\n";
1410
- $o = &$this->objects[$id];
1411
- foreach ($o['info'] as $k => $v) {
1412
- $res .= "/$k (";
1413
-
1414
- // dates must be outputted as-is, without Unicode transformations
1415
- if ($k !== 'CreationDate' && $k !== 'ModDate') {
1416
- $v = $this->filterText($v, true, false);
1417
- }
1418
-
1419
- if ($encrypted) {
1420
- $v = $this->ARC4($v);
1421
- }
1422
-
1423
- $res .= $v;
1424
- $res .= ")\n";
1425
- }
1426
-
1427
- $res .= ">>\nendobj";
1428
-
1429
- return $res;
1430
- }
1431
-
1432
- return null;
1433
- }
1434
-
1435
- /**
1436
- * an action object, used to link to URLS initially
1437
- *
1438
- * @param $id
1439
- * @param $action
1440
- * @param string $options
1441
- * @return null|string
1442
- */
1443
- protected function o_action($id, $action, $options = '')
1444
- {
1445
- if ($action !== 'new') {
1446
- $o = &$this->objects[$id];
1447
- }
1448
-
1449
- switch ($action) {
1450
- case 'new':
1451
- if (is_array($options)) {
1452
- $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => $options['type']);
1453
- } else {
1454
- // then assume a URI action
1455
- $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => 'URI');
1456
- }
1457
- break;
1458
-
1459
- case 'out':
1460
- if ($this->encrypted) {
1461
- $this->encryptInit($id);
1462
- }
1463
-
1464
- $res = "\n$id 0 obj\n<< /Type /Action";
1465
- switch ($o['type']) {
1466
- case 'ilink':
1467
- if (!isset($this->destinations[(string)$o['info']['label']])) {
1468
- break;
1469
- }
1470
-
1471
- // there will be an 'label' setting, this is the name of the destination
1472
- $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1473
- break;
1474
-
1475
- case 'URI':
1476
- $res .= "\n/S /URI\n/URI (";
1477
- if ($this->encrypted) {
1478
- $res .= $this->filterText($this->ARC4($o['info']), false, false);
1479
- } else {
1480
- $res .= $this->filterText($o['info'], false, false);
1481
- }
1482
-
1483
- $res .= ")";
1484
- break;
1485
- }
1486
-
1487
- $res .= "\n>>\nendobj";
1488
-
1489
- return $res;
1490
- }
1491
-
1492
- return null;
1493
- }
1494
-
1495
- /**
1496
- * an annotation object, this will add an annotation to the current page.
1497
- * initially will support just link annotations
1498
- *
1499
- * @param $id
1500
- * @param $action
1501
- * @param string $options
1502
- * @return null|string
1503
- */
1504
- protected function o_annotation($id, $action, $options = '')
1505
- {
1506
- if ($action !== 'new') {
1507
- $o = &$this->objects[$id];
1508
- }
1509
-
1510
- switch ($action) {
1511
- case 'new':
1512
- // add the annotation to the current page
1513
- $pageId = $this->currentPage;
1514
- $this->o_page($pageId, 'annot', $id);
1515
-
1516
- // and add the action object which is going to be required
1517
- switch ($options['type']) {
1518
- case 'link':
1519
- $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
1520
- $this->numObj++;
1521
- $this->o_action($this->numObj, 'new', $options['url']);
1522
- $this->objects[$id]['info']['actionId'] = $this->numObj;
1523
- break;
1524
-
1525
- case 'ilink':
1526
- // this is to a named internal link
1527
- $label = $options['label'];
1528
- $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
1529
- $this->numObj++;
1530
- $this->o_action($this->numObj, 'new', array('type' => 'ilink', 'label' => $label));
1531
- $this->objects[$id]['info']['actionId'] = $this->numObj;
1532
- break;
1533
- }
1534
- break;
1535
-
1536
- case 'out':
1537
- $res = "\n$id 0 obj\n<< /Type /Annot";
1538
- switch ($o['info']['type']) {
1539
- case 'link':
1540
- case 'ilink':
1541
- $res .= "\n/Subtype /Link";
1542
- break;
1543
- }
1544
- $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1545
- $res .= "\n/Border [0 0 0]";
1546
- $res .= "\n/H /I";
1547
- $res .= "\n/Rect [ ";
1548
-
1549
- foreach ($o['info']['rect'] as $v) {
1550
- $res .= sprintf("%.4F ", $v);
1551
- }
1552
-
1553
- $res .= "]";
1554
- $res .= "\n>>\nendobj";
1555
-
1556
- return $res;
1557
- }
1558
-
1559
- return null;
1560
- }
1561
-
1562
- /**
1563
- * a page object, it also creates a contents object to hold its contents
1564
- *
1565
- * @param $id
1566
- * @param $action
1567
- * @param string $options
1568
- * @return null|string
1569
- */
1570
- protected function o_page($id, $action, $options = '')
1571
- {
1572
- if ($action !== 'new') {
1573
- $o = &$this->objects[$id];
1574
- }
1575
-
1576
- switch ($action) {
1577
- case 'new':
1578
- $this->numPages++;
1579
- $this->objects[$id] = array(
1580
- 't' => 'page',
1581
- 'info' => array(
1582
- 'parent' => $this->currentNode,
1583
- 'pageNum' => $this->numPages,
1584
- 'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1585
- )
1586
- );
1587
-
1588
- if (is_array($options)) {
1589
- // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1590
- $options['id'] = $id;
1591
- $this->o_pages($this->currentNode, 'page', $options);
1592
- } else {
1593
- $this->o_pages($this->currentNode, 'page', $id);
1594
- }
1595
-
1596
- $this->currentPage = $id;
1597
- //make a contents object to go with this page
1598
- $this->numObj++;
1599
- $this->o_contents($this->numObj, 'new', $id);
1600
- $this->currentContents = $this->numObj;
1601
- $this->objects[$id]['info']['contents'] = array();
1602
- $this->objects[$id]['info']['contents'][] = $this->numObj;
1603
-
1604
- $match = ($this->numPages % 2 ? 'odd' : 'even');
1605
- foreach ($this->addLooseObjects as $oId => $target) {
1606
- if ($target === 'all' || $match === $target) {
1607
- $this->objects[$id]['info']['contents'][] = $oId;
1608
- }
1609
- }
1610
- break;
1611
-
1612
- case 'content':
1613
- $o['info']['contents'][] = $options;
1614
- break;
1615
-
1616
- case 'annot':
1617
- // add an annotation to this page
1618
- if (!isset($o['info']['annot'])) {
1619
- $o['info']['annot'] = array();
1620
- }
1621
-
1622
- // $options should contain the id of the annotation dictionary
1623
- $o['info']['annot'][] = $options;
1624
- break;
1625
-
1626
- case 'out':
1627
- $res = "\n$id 0 obj\n<< /Type /Page";
1628
- if (isset($o['info']['mediaBox'])) {
1629
- $tmp = $o['info']['mediaBox'];
1630
- $res .= "\n/MediaBox [" . sprintf(
1631
- '%.3F %.3F %.3F %.3F',
1632
- $tmp[0],
1633
- $tmp[1],
1634
- $tmp[2],
1635
- $tmp[3]
1636
- ) . ']';
1637
- }
1638
- $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1639
-
1640
- if (isset($o['info']['annot'])) {
1641
- $res .= "\n/Annots [";
1642
- foreach ($o['info']['annot'] as $aId) {
1643
- $res .= " $aId 0 R";
1644
- }
1645
- $res .= " ]";
1646
- }
1647
-
1648
- $count = count($o['info']['contents']);
1649
- if ($count == 1) {
1650
- $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1651
- } else {
1652
- if ($count > 1) {
1653
- $res .= "\n/Contents [\n";
1654
-
1655
- // reverse the page contents so added objects are below normal content
1656
- //foreach (array_reverse($o['info']['contents']) as $cId) {
1657
- // Back to normal now that I've got transparency working --Benj
1658
- foreach ($o['info']['contents'] as $cId) {
1659
- $res .= "$cId 0 R\n";
1660
- }
1661
- $res .= "]";
1662
- }
1663
- }
1664
-
1665
- $res .= "\n>>\nendobj";
1666
-
1667
- return $res;
1668
- }
1669
-
1670
- return null;
1671
- }
1672
-
1673
- /**
1674
- * the contents objects hold all of the content which appears on pages
1675
- *
1676
- * @param $id
1677
- * @param $action
1678
- * @param string|array $options
1679
- * @return null|string
1680
- */
1681
- protected function o_contents($id, $action, $options = '')
1682
- {
1683
- if ($action !== 'new') {
1684
- $o = &$this->objects[$id];
1685
- }
1686
-
1687
- switch ($action) {
1688
- case 'new':
1689
- $this->objects[$id] = array('t' => 'contents', 'c' => '', 'info' => array());
1690
- if (mb_strlen($options, '8bit') && intval($options)) {
1691
- // then this contents is the primary for a page
1692
- $this->objects[$id]['onPage'] = $options;
1693
- } else {
1694
- if ($options === 'raw') {
1695
- // then this page contains some other type of system object
1696
- $this->objects[$id]['raw'] = 1;
1697
- }
1698
- }
1699
- break;
1700
-
1701
- case 'add':
1702
- // add more options to the declaration
1703
- foreach ($options as $k => $v) {
1704
- $o['info'][$k] = $v;
1705
- }
1706
-
1707
- case 'out':
1708
- $tmp = $o['c'];
1709
- $res = "\n$id 0 obj\n";
1710
-
1711
- if (isset($this->objects[$id]['raw'])) {
1712
- $res .= $tmp;
1713
- } else {
1714
- $res .= "<<";
1715
- if ($this->compressionReady && $this->options['compression']) {
1716
- // then implement ZLIB based compression on this content stream
1717
- $res .= " /Filter /FlateDecode";
1718
- $tmp = gzcompress($tmp, 6);
1719
- }
1720
-
1721
- if ($this->encrypted) {
1722
- $this->encryptInit($id);
1723
- $tmp = $this->ARC4($tmp);
1724
- }
1725
-
1726
- foreach ($o['info'] as $k => $v) {
1727
- $res .= "\n/$k $v";
1728
- }
1729
-
1730
- $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
1731
- }
1732
-
1733
- $res .= "\nendobj";
1734
-
1735
- return $res;
1736
- }
1737
-
1738
- return null;
1739
- }
1740
-
1741
- /**
1742
- * @param $id
1743
- * @param $action
1744
- * @return string|null
1745
- */
1746
- protected function o_embedjs($id, $action)
1747
- {
1748
- switch ($action) {
1749
- case 'new':
1750
- $this->objects[$id] = array(
1751
- 't' => 'embedjs',
1752
- 'info' => array(
1753
- 'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
1754
- )
1755
- );
1756
- break;
1757
-
1758
- case 'out':
1759
- $o = &$this->objects[$id];
1760
- $res = "\n$id 0 obj\n<< ";
1761
- foreach ($o['info'] as $k => $v) {
1762
- $res .= "\n/$k $v";
1763
- }
1764
- $res .= "\n>>\nendobj";
1765
-
1766
- return $res;
1767
- }
1768
-
1769
- return null;
1770
- }
1771
-
1772
- /**
1773
- * @param $id
1774
- * @param $action
1775
- * @param string $code
1776
- * @return null|string
1777
- */
1778
- protected function o_javascript($id, $action, $code = '')
1779
- {
1780
- switch ($action) {
1781
- case 'new':
1782
- $this->objects[$id] = array(
1783
- 't' => 'javascript',
1784
- 'info' => array(
1785
- 'S' => '/JavaScript',
1786
- 'JS' => '(' . $this->filterText($code, true, false) . ')',
1787
- )
1788
- );
1789
- break;
1790
-
1791
- case 'out':
1792
- $o = &$this->objects[$id];
1793
- $res = "\n$id 0 obj\n<< ";
1794
-
1795
- foreach ($o['info'] as $k => $v) {
1796
- $res .= "\n/$k $v";
1797
- }
1798
- $res .= "\n>>\nendobj";
1799
-
1800
- return $res;
1801
- }
1802
-
1803
- return null;
1804
- }
1805
-
1806
- /**
1807
- * an image object, will be an XObject in the document, includes description and data
1808
- *
1809
- * @param $id
1810
- * @param $action
1811
- * @param string $options
1812
- * @return null|string
1813
- */
1814
- protected function o_image($id, $action, $options = '')
1815
- {
1816
- switch ($action) {
1817
- case 'new':
1818
- // make the new object
1819
- $this->objects[$id] = array('t' => 'image', 'data' => &$options['data'], 'info' => array());
1820
-
1821
- $info =& $this->objects[$id]['info'];
1822
-
1823
- $info['Type'] = '/XObject';
1824
- $info['Subtype'] = '/Image';
1825
- $info['Width'] = $options['iw'];
1826
- $info['Height'] = $options['ih'];
1827
-
1828
- if (isset($options['masked']) && $options['masked']) {
1829
- $info['SMask'] = ($this->numObj - 1) . ' 0 R';
1830
- }
1831
-
1832
- if (!isset($options['type']) || $options['type'] === 'jpg') {
1833
- if (!isset($options['channels'])) {
1834
- $options['channels'] = 3;
1835
- }
1836
-
1837
- switch ($options['channels']) {
1838
- case 1:
1839
- $info['ColorSpace'] = '/DeviceGray';
1840
- break;
1841
- case 4:
1842
- $info['ColorSpace'] = '/DeviceCMYK';
1843
- break;
1844
- default:
1845
- $info['ColorSpace'] = '/DeviceRGB';
1846
- break;
1847
- }
1848
-
1849
- if ($info['ColorSpace'] === '/DeviceCMYK') {
1850
- $info['Decode'] = '[1 0 1 0 1 0 1 0]';
1851
- }
1852
-
1853
- $info['Filter'] = '/DCTDecode';
1854
- $info['BitsPerComponent'] = 8;
1855
- } else {
1856
- if ($options['type'] === 'png') {
1857
- $info['Filter'] = '/FlateDecode';
1858
- $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
1859
-
1860
- if ($options['isMask']) {
1861
- $info['ColorSpace'] = '/DeviceGray';
1862
- } else {
1863
- if (mb_strlen($options['pdata'], '8bit')) {
1864
- $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
1865
- $this->numObj++;
1866
- $this->o_contents($this->numObj, 'new');
1867
- $this->objects[$this->numObj]['c'] = $options['pdata'];
1868
- $tmp .= $this->numObj . ' 0 R';
1869
- $tmp .= ' ]';
1870
- $info['ColorSpace'] = $tmp;
1871
-
1872
- if (isset($options['transparency'])) {
1873
- $transparency = $options['transparency'];
1874
- switch ($transparency['type']) {
1875
- case 'indexed':
1876
- $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
1877
- $info['Mask'] = $tmp;
1878
- break;
1879
-
1880
- case 'color-key':
1881
- $tmp = ' [ ' .
1882
- $transparency['r'] . ' ' . $transparency['r'] .
1883
- $transparency['g'] . ' ' . $transparency['g'] .
1884
- $transparency['b'] . ' ' . $transparency['b'] .
1885
- ' ] ';
1886
- $info['Mask'] = $tmp;
1887
- break;
1888
- }
1889
- }
1890
- } else {
1891
- if (isset($options['transparency'])) {
1892
- $transparency = $options['transparency'];
1893
-
1894
- switch ($transparency['type']) {
1895
- case 'indexed':
1896
- $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
1897
- $info['Mask'] = $tmp;
1898
- break;
1899
-
1900
- case 'color-key':
1901
- $tmp = ' [ ' .
1902
- $transparency['r'] . ' ' . $transparency['r'] . ' ' .
1903
- $transparency['g'] . ' ' . $transparency['g'] . ' ' .
1904
- $transparency['b'] . ' ' . $transparency['b'] .
1905
- ' ] ';
1906
- $info['Mask'] = $tmp;
1907
- break;
1908
- }
1909
- }
1910
- $info['ColorSpace'] = '/' . $options['color'];
1911
- }
1912
- }
1913
-
1914
- $info['BitsPerComponent'] = $options['bitsPerComponent'];
1915
- }
1916
- }
1917
-
1918
- // assign it a place in the named resource dictionary as an external object, according to
1919
- // the label passed in with it.
1920
- $this->o_pages($this->currentNode, 'xObject', array('label' => $options['label'], 'objNum' => $id));
1921
-
1922
- // also make sure that we have the right procset object for it.
1923
- $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
1924
- break;
1925
-
1926
- case 'out':
1927
- $o = &$this->objects[$id];
1928
- $tmp = &$o['data'];
1929
- $res = "\n$id 0 obj\n<<";
1930
-
1931
- foreach ($o['info'] as $k => $v) {
1932
- $res .= "\n/$k $v";
1933
- }
1934
-
1935
- if ($this->encrypted) {
1936
- $this->encryptInit($id);
1937
- $tmp = $this->ARC4($tmp);
1938
- }
1939
-
1940
- $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
1941
-
1942
- return $res;
1943
- }
1944
-
1945
- return null;
1946
- }
1947
-
1948
- /**
1949
- * graphics state object
1950
- *
1951
- * @param $id
1952
- * @param $action
1953
- * @param string $options
1954
- * @return null|string
1955
- */
1956
- protected function o_extGState($id, $action, $options = "")
1957
- {
1958
- static $valid_params = array(
1959
- "LW",
1960
- "LC",
1961
- "LC",
1962
- "LJ",
1963
- "ML",
1964
- "D",
1965
- "RI",
1966
- "OP",
1967
- "op",
1968
- "OPM",
1969
- "Font",
1970
- "BG",
1971
- "BG2",
1972
- "UCR",
1973
- "TR",
1974
- "TR2",
1975
- "HT",
1976
- "FL",
1977
- "SM",
1978
- "SA",
1979
- "BM",
1980
- "SMask",
1981
- "CA",
1982
- "ca",
1983
- "AIS",
1984
- "TK"
1985
- );
1986
-
1987
- switch ($action) {
1988
- case "new":
1989
- $this->objects[$id] = array('t' => 'extGState', 'info' => $options);
1990
-
1991
- // Tell the pages about the new resource
1992
- $this->numStates++;
1993
- $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates));
1994
- break;
1995
-
1996
- case "out":
1997
- $o = &$this->objects[$id];
1998
- $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
1999
-
2000
- foreach ($o["info"] as $k => $v) {
2001
- if (!in_array($k, $valid_params)) {
2002
- continue;
2003
- }
2004
- $res .= "/$k $v\n";
2005
- }
2006
-
2007
- $res .= ">>\nendobj";
2008
-
2009
- return $res;
2010
- }
2011
-
2012
- return null;
2013
- }
2014
-
2015
- /**
2016
- * encryption object.
2017
- *
2018
- * @param $id
2019
- * @param $action
2020
- * @param string $options
2021
- * @return string|null
2022
- */
2023
- protected function o_encryption($id, $action, $options = '')
2024
- {
2025
- switch ($action) {
2026
- case 'new':
2027
- // make the new object
2028
- $this->objects[$id] = array('t' => 'encryption', 'info' => $options);
2029
- $this->arc4_objnum = $id;
2030
- break;
2031
-
2032
- case 'keys':
2033
- // figure out the additional parameters required
2034
- $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2035
- . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2036
- . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2037
- . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2038
-
2039
- $info = $this->objects[$id]['info'];
2040
-
2041
- $len = mb_strlen($info['owner'], '8bit');
2042
-
2043
- if ($len > 32) {
2044
- $owner = substr($info['owner'], 0, 32);
2045
- } else {
2046
- if ($len < 32) {
2047
- $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2048
- } else {
2049
- $owner = $info['owner'];
2050
- }
2051
- }
2052
-
2053
- $len = mb_strlen($info['user'], '8bit');
2054
- if ($len > 32) {
2055
- $user = substr($info['user'], 0, 32);
2056
- } else {
2057
- if ($len < 32) {
2058
- $user = $info['user'] . substr($pad, 0, 32 - $len);
2059
- } else {
2060
- $user = $info['user'];
2061
- }
2062
- }
2063
-
2064
- $tmp = $this->md5_16($owner);
2065
- $okey = substr($tmp, 0, 5);
2066
- $this->ARC4_init($okey);
2067
- $ovalue = $this->ARC4($user);
2068
- $this->objects[$id]['info']['O'] = $ovalue;
2069
-
2070
- // now make the u value, phew.
2071
- $tmp = $this->md5_16(
2072
- $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2073
- );
2074
-
2075
- $ukey = substr($tmp, 0, 5);
2076
- $this->ARC4_init($ukey);
2077
- $this->encryptionKey = $ukey;
2078
- $this->encrypted = true;
2079
- $uvalue = $this->ARC4($pad);
2080
- $this->objects[$id]['info']['U'] = $uvalue;
2081
- // initialize the arc4 array
2082
- break;
2083
-
2084
- case 'out':
2085
- $o = &$this->objects[$id];
2086
-
2087
- $res = "\n$id 0 obj\n<<";
2088
- $res .= "\n/Filter /Standard";
2089
- $res .= "\n/V 1";
2090
- $res .= "\n/R 2";
2091
- $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2092
- $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2093
- // and the p-value needs to be converted to account for the twos-complement approach
2094
- $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2095
- $res .= "\n/P " . ($o['info']['p']);
2096
- $res .= "\n>>\nendobj";
2097
-
2098
- return $res;
2099
- }
2100
-
2101
- return null;
2102
- }
2103
-
2104
- /**
2105
- * ARC4 functions
2106
- * A series of function to implement ARC4 encoding in PHP
2107
- */
2108
-
2109
- /**
2110
- * calculate the 16 byte version of the 128 bit md5 digest of the string
2111
- *
2112
- * @param $string
2113
- * @return string
2114
- */
2115
- function md5_16($string)
2116
- {
2117
- $tmp = md5($string);
2118
- $out = '';
2119
- for ($i = 0; $i <= 30; $i = $i + 2) {
2120
- $out .= chr(hexdec(substr($tmp, $i, 2)));
2121
- }
2122
-
2123
- return $out;
2124
- }
2125
-
2126
- /**
2127
- * initialize the encryption for processing a particular object
2128
- *
2129
- * @param $id
2130
- */
2131
- function encryptInit($id)
2132
- {
2133
- $tmp = $this->encryptionKey;
2134
- $hex = dechex($id);
2135
- if (mb_strlen($hex, '8bit') < 6) {
2136
- $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2137
- }
2138
- $tmp .= chr(hexdec(substr($hex, 4, 2)))
2139
- . chr(hexdec(substr($hex, 2, 2)))
2140
- . chr(hexdec(substr($hex, 0, 2)))
2141
- . chr(0)
2142
- . chr(0)
2143
- ;
2144
- $key = $this->md5_16($tmp);
2145
- $this->ARC4_init(substr($key, 0, 10));
2146
- }
2147
-
2148
- /**
2149
- * initialize the ARC4 encryption
2150
- *
2151
- * @param string $key
2152
- */
2153
- function ARC4_init($key = '')
2154
- {
2155
- $this->arc4 = '';
2156
-
2157
- // setup the control array
2158
- if (mb_strlen($key, '8bit') == 0) {
2159
- return;
2160
- }
2161
-
2162
- $k = '';
2163
- while (mb_strlen($k, '8bit') < 256) {
2164
- $k .= $key;
2165
- }
2166
-
2167
- $k = substr($k, 0, 256);
2168
- for ($i = 0; $i < 256; $i++) {
2169
- $this->arc4 .= chr($i);
2170
- }
2171
-
2172
- $j = 0;
2173
-
2174
- for ($i = 0; $i < 256; $i++) {
2175
- $t = $this->arc4[$i];
2176
- $j = ($j + ord($t) + ord($k[$i])) % 256;
2177
- $this->arc4[$i] = $this->arc4[$j];
2178
- $this->arc4[$j] = $t;
2179
- }
2180
- }
2181
-
2182
- /**
2183
- * ARC4 encrypt a text string
2184
- *
2185
- * @param $text
2186
- * @return string
2187
- */
2188
- function ARC4($text)
2189
- {
2190
- $len = mb_strlen($text, '8bit');
2191
- $a = 0;
2192
- $b = 0;
2193
- $c = $this->arc4;
2194
- $out = '';
2195
- for ($i = 0; $i < $len; $i++) {
2196
- $a = ($a + 1) % 256;
2197
- $t = $c[$a];
2198
- $b = ($b + ord($t)) % 256;
2199
- $c[$a] = $c[$b];
2200
- $c[$b] = $t;
2201
- $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
2202
- $out .= chr(ord($text[$i]) ^ $k);
2203
- }
2204
-
2205
- return $out;
2206
- }
2207
-
2208
- /**
2209
- * functions which can be called to adjust or add to the document
2210
- */
2211
-
2212
- /**
2213
- * add a link in the document to an external URL
2214
- *
2215
- * @param $url
2216
- * @param $x0
2217
- * @param $y0
2218
- * @param $x1
2219
- * @param $y1
2220
- */
2221
- function addLink($url, $x0, $y0, $x1, $y1)
2222
- {
2223
- $this->numObj++;
2224
- $info = array('type' => 'link', 'url' => $url, 'rect' => array($x0, $y0, $x1, $y1));
2225
- $this->o_annotation($this->numObj, 'new', $info);
2226
- }
2227
-
2228
- /**
2229
- * add a link in the document to an internal destination (ie. within the document)
2230
- *
2231
- * @param $label
2232
- * @param $x0
2233
- * @param $y0
2234
- * @param $x1
2235
- * @param $y1
2236
- */
2237
- function addInternalLink($label, $x0, $y0, $x1, $y1)
2238
- {
2239
- $this->numObj++;
2240
- $info = array('type' => 'ilink', 'label' => $label, 'rect' => array($x0, $y0, $x1, $y1));
2241
- $this->o_annotation($this->numObj, 'new', $info);
2242
- }
2243
-
2244
- /**
2245
- * set the encryption of the document
2246
- * can be used to turn it on and/or set the passwords which it will have.
2247
- * also the functions that the user will have are set here, such as print, modify, add
2248
- *
2249
- * @param string $userPass
2250
- * @param string $ownerPass
2251
- * @param array $pc
2252
- */
2253
- function setEncryption($userPass = '', $ownerPass = '', $pc = array())
2254
- {
2255
- $p = bindec("11000000");
2256
-
2257
- $options = array('print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32);
2258
-
2259
- foreach ($pc as $k => $v) {
2260
- if ($v && isset($options[$k])) {
2261
- $p += $options[$k];
2262
- } else {
2263
- if (isset($options[$v])) {
2264
- $p += $options[$v];
2265
- }
2266
- }
2267
- }
2268
-
2269
- // implement encryption on the document
2270
- if ($this->arc4_objnum == 0) {
2271
- // then the block does not exist already, add it.
2272
- $this->numObj++;
2273
- if (mb_strlen($ownerPass) == 0) {
2274
- $ownerPass = $userPass;
2275
- }
2276
-
2277
- $this->o_encryption($this->numObj, 'new', array('user' => $userPass, 'owner' => $ownerPass, 'p' => $p));
2278
- }
2279
- }
2280
-
2281
- /**
2282
- * should be used for internal checks, not implemented as yet
2283
- */
2284
- function checkAllHere()
2285
- {
2286
- }
2287
-
2288
- /**
2289
- * return the pdf stream as a string returned from the function
2290
- *
2291
- * @param bool $debug
2292
- * @return string
2293
- */
2294
- function output($debug = false)
2295
- {
2296
- if ($debug) {
2297
- // turn compression off
2298
- $this->options['compression'] = false;
2299
- }
2300
-
2301
- if ($this->javascript) {
2302
- $this->numObj++;
2303
-
2304
- $js_id = $this->numObj;
2305
- $this->o_embedjs($js_id, 'new');
2306
- $this->o_javascript(++$this->numObj, 'new', $this->javascript);
2307
-
2308
- $id = $this->catalogId;
2309
-
2310
- $this->o_catalog($id, 'javascript', $js_id);
2311
- }
2312
-
2313
- if ($this->fileIdentifier === '') {
2314
- $tmp = implode('', $this->objects[$this->infoObject]['info']);
2315
- $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
2316
- }
2317
-
2318
- if ($this->arc4_objnum) {
2319
- $this->o_encryption($this->arc4_objnum, 'keys');
2320
- $this->ARC4_init($this->encryptionKey);
2321
- }
2322
-
2323
- $this->checkAllHere();
2324
-
2325
- $xref = array();
2326
- $content = '%PDF-1.3';
2327
- $pos = mb_strlen($content, '8bit');
2328
-
2329
- foreach ($this->objects as $k => $v) {
2330
- $tmp = 'o_' . $v['t'];
2331
- $cont = $this->$tmp($k, 'out');
2332
- $content .= $cont;
2333
- $xref[] = $pos + 1; //+1 to account for \n at the start of each object
2334
- $pos += mb_strlen($cont, '8bit');
2335
- }
2336
-
2337
- $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
2338
-
2339
- foreach ($xref as $p) {
2340
- $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
2341
- }
2342
-
2343
- $content .= "trailer\n<<\n" .
2344
- '/Size ' . (count($xref) + 1) . "\n" .
2345
- '/Root 1 0 R' . "\n" .
2346
- '/Info ' . $this->infoObject . " 0 R\n"
2347
- ;
2348
-
2349
- // if encryption has been applied to this document then add the marker for this dictionary
2350
- if ($this->arc4_objnum > 0) {
2351
- $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
2352
- }
2353
-
2354
- $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
2355
-
2356
- // account for \n added at start of xref table
2357
- $pos++;
2358
-
2359
- $content .= ">>\nstartxref\n$pos\n%%EOF\n";
2360
-
2361
- return $content;
2362
- }
2363
-
2364
- /**
2365
- * initialize a new document
2366
- * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
2367
- * this function is called automatically by the constructor function
2368
- *
2369
- * @param array $pageSize
2370
- */
2371
- private function newDocument($pageSize = array(0, 0, 612, 792))
2372
- {
2373
- $this->numObj = 0;
2374
- $this->objects = array();
2375
-
2376
- $this->numObj++;
2377
- $this->o_catalog($this->numObj, 'new');
2378
-
2379
- $this->numObj++;
2380
- $this->o_outlines($this->numObj, 'new');
2381
-
2382
- $this->numObj++;
2383
- $this->o_pages($this->numObj, 'new');
2384
-
2385
- $this->o_pages($this->numObj, 'mediaBox', $pageSize);
2386
- $this->currentNode = 3;
2387
-
2388
- $this->numObj++;
2389
- $this->o_procset($this->numObj, 'new');
2390
-
2391
- $this->numObj++;
2392
- $this->o_info($this->numObj, 'new');
2393
-
2394
- $this->numObj++;
2395
- $this->o_page($this->numObj, 'new');
2396
-
2397
- // need to store the first page id as there is no way to get it to the user during
2398
- // startup
2399
- $this->firstPageId = $this->currentContents;
2400
- }
2401
-
2402
- /**
2403
- * open the font file and return a php structure containing it.
2404
- * first check if this one has been done before and saved in a form more suited to php
2405
- * note that if a php serialized version does not exist it will try and make one, but will
2406
- * require write access to the directory to do it... it is MUCH faster to have these serialized
2407
- * files.
2408
- *
2409
- * @param $font
2410
- */
2411
- private function openFont($font)
2412
- {
2413
- // assume that $font contains the path and file but not the extension
2414
- $name = basename($font);
2415
- $dir = dirname($font) . '/';
2416
-
2417
- $fontcache = $this->fontcache;
2418
- if ($fontcache == '') {
2419
- $fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\");
2420
- }
2421
-
2422
- //$name filename without folder and extension of font metrics
2423
- //$dir folder of font metrics
2424
- //$fontcache folder of runtime created php serialized version of font metrics.
2425
- // If this is not given, the same folder as the font metrics will be used.
2426
- // Storing and reusing serialized versions improves speed much
2427
-
2428
- $this->addMessage("openFont: $font - $name");
2429
-
2430
- if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
2431
- $metrics_name = "$name.afm";
2432
- } else {
2433
- $metrics_name = "$name.ufm";
2434
- }
2435
-
2436
- $cache_name = "$metrics_name.php";
2437
- $this->addMessage("metrics: $metrics_name, cache: $cache_name");
2438
-
2439
- if (file_exists($fontcache . '/' . $cache_name)) {
2440
- $this->addMessage("openFont: php file exists $fontcache/$cache_name");
2441
- $this->fonts[$font] = require($fontcache . '/' . $cache_name);
2442
-
2443
- if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
2444
- // if the font file is old, then clear it out and prepare for re-creation
2445
- $this->addMessage('openFont: clear out, make way for new version.');
2446
- $this->fonts[$font] = null;
2447
- unset($this->fonts[$font]);
2448
- }
2449
- } else {
2450
- $old_cache_name = "php_$metrics_name";
2451
- if (file_exists($fontcache . '/' . $old_cache_name)) {
2452
- $this->addMessage(
2453
- "openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
2454
- );
2455
- $old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
2456
- file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
2457
-
2458
- $this->openFont($font);
2459
- return;
2460
- }
2461
- }
2462
-
2463
- if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
2464
- // then rebuild the php_<font>.afm file from the <font>.afm file
2465
- $this->addMessage("openFont: build php file from $dir$metrics_name");
2466
- $data = array();
2467
-
2468
- // 20 => 'space'
2469
- $data['codeToName'] = array();
2470
-
2471
- // Since we're not going to enable Unicode for the core fonts we need to use a font-based
2472
- // setting for Unicode support rather than a global setting.
2473
- $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
2474
-
2475
- $cidtogid = '';
2476
- if ($data['isUnicode']) {
2477
- $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
2478
- }
2479
-
2480
- $file = file($dir . $metrics_name);
2481
-
2482
- foreach ($file as $rowA) {
2483
- $row = trim($rowA);
2484
- $pos = strpos($row, ' ');
2485
-
2486
- if ($pos) {
2487
- // then there must be some keyword
2488
- $key = substr($row, 0, $pos);
2489
- switch ($key) {
2490
- case 'FontName':
2491
- case 'FullName':
2492
- case 'FamilyName':
2493
- case 'PostScriptName':
2494
- case 'Weight':
2495
- case 'ItalicAngle':
2496
- case 'IsFixedPitch':
2497
- case 'CharacterSet':
2498
- case 'UnderlinePosition':
2499
- case 'UnderlineThickness':
2500
- case 'Version':
2501
- case 'EncodingScheme':
2502
- case 'CapHeight':
2503
- case 'XHeight':
2504
- case 'Ascender':
2505
- case 'Descender':
2506
- case 'StdHW':
2507
- case 'StdVW':
2508
- case 'StartCharMetrics':
2509
- case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big.
2510
- $data[$key] = trim(substr($row, $pos));
2511
- break;
2512
-
2513
- case 'FontBBox':
2514
- $data[$key] = explode(' ', trim(substr($row, $pos)));
2515
- break;
2516
-
2517
- //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
2518
- case 'C': // Found in AFM files
2519
- $bits = explode(';', trim($row));
2520
- $dtmp = array();
2521
-
2522
- foreach ($bits as $bit) {
2523
- $bits2 = explode(' ', trim($bit));
2524
- if (mb_strlen($bits2[0], '8bit') == 0) {
2525
- continue;
2526
- }
2527
-
2528
- if (count($bits2) > 2) {
2529
- $dtmp[$bits2[0]] = array();
2530
- for ($i = 1; $i < count($bits2); $i++) {
2531
- $dtmp[$bits2[0]][] = $bits2[$i];
2532
- }
2533
- } else {
2534
- if (count($bits2) == 2) {
2535
- $dtmp[$bits2[0]] = $bits2[1];
2536
- }
2537
- }
2538
- }
2539
-
2540
- $c = (int)$dtmp['C'];
2541
- $n = $dtmp['N'];
2542
- $width = floatval($dtmp['WX']);
2543
-
2544
- if ($c >= 0) {
2545
- if ($c != hexdec($n)) {
2546
- $data['codeToName'][$c] = $n;
2547
- }
2548
- $data['C'][$c] = $width;
2549
- } else {
2550
- $data['C'][$n] = $width;
2551
- }
2552
-
2553
- if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') {
2554
- $data['MissingWidth'] = $width;
2555
- }
2556
-
2557
- break;
2558
-
2559
- // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
2560
- case 'U': // Found in UFM files
2561
- if (!$data['isUnicode']) {
2562
- break;
2563
- }
2564
-
2565
- $bits = explode(';', trim($row));
2566
- $dtmp = array();
2567
-
2568
- foreach ($bits as $bit) {
2569
- $bits2 = explode(' ', trim($bit));
2570
- if (mb_strlen($bits2[0], '8bit') === 0) {
2571
- continue;
2572
- }
2573
-
2574
- if (count($bits2) > 2) {
2575
- $dtmp[$bits2[0]] = array();
2576
- for ($i = 1; $i < count($bits2); $i++) {
2577
- $dtmp[$bits2[0]][] = $bits2[$i];
2578
- }
2579
- } else {
2580
- if (count($bits2) == 2) {
2581
- $dtmp[$bits2[0]] = $bits2[1];
2582
- }
2583
- }
2584
- }
2585
-
2586
- $c = (int)$dtmp['U'];
2587
- $n = $dtmp['N'];
2588
- $glyph = $dtmp['G'];
2589
- $width = floatval($dtmp['WX']);
2590
-
2591
- if ($c >= 0) {
2592
- // Set values in CID to GID map
2593
- if ($c >= 0 && $c < 0xFFFF && $glyph) {
2594
- $cidtogid[$c * 2] = chr($glyph >> 8);
2595
- $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
2596
- }
2597
-
2598
- if ($c != hexdec($n)) {
2599
- $data['codeToName'][$c] = $n;
2600
- }
2601
- $data['C'][$c] = $width;
2602
- } else {
2603
- $data['C'][$n] = $width;
2604
- }
2605
-
2606
- if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') {
2607
- $data['MissingWidth'] = $width;
2608
- }
2609
-
2610
- break;
2611
-
2612
- case 'KPX':
2613
- break; // don't include them as they are not used yet
2614
- //KPX Adieresis yacute -40
2615
- /*$bits = explode(' ', trim($row));
2616
- $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
2617
- break;*/
2618
- }
2619
- }
2620
- }
2621
-
2622
- if ($this->compressionReady && $this->options['compression']) {
2623
- // then implement ZLIB based compression on CIDtoGID string
2624
- $data['CIDtoGID_Compressed'] = true;
2625
- $cidtogid = gzcompress($cidtogid, 6);
2626
- }
2627
- $data['CIDtoGID'] = base64_encode($cidtogid);
2628
- $data['_version_'] = $this->fontcacheVersion;
2629
- $this->fonts[$font] = $data;
2630
-
2631
- //Because of potential trouble with php safe mode, expect that the folder already exists.
2632
- //If not existing, this will hit performance because of missing cached results.
2633
- if (is_dir($fontcache) && is_writable($fontcache)) {
2634
- file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
2635
- }
2636
- $data = null;
2637
- }
2638
-
2639
- if (!isset($this->fonts[$font])) {
2640
- $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
2641
- }
2642
-
2643
- //pre_r($this->messages);
2644
- }
2645
-
2646
- /**
2647
- * if the font is not loaded then load it and make the required object
2648
- * else just make it the current font
2649
- * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
2650
- * note that encoding='none' will need to be used for symbolic fonts
2651
- * and 'differences' => an array of mappings between numbers 0->255 and character names.
2652
- *
2653
- * @param $fontName
2654
- * @param string $encoding
2655
- * @param bool $set
2656
- * @return int
2657
- */
2658
- function selectFont($fontName, $encoding = '', $set = true)
2659
- {
2660
- $ext = substr($fontName, -4);
2661
- if ($ext === '.afm' || $ext === '.ufm') {
2662
- $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
2663
- }
2664
-
2665
- if (!isset($this->fonts[$fontName])) {
2666
- $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
2667
-
2668
- // load the file
2669
- $this->openFont($fontName);
2670
-
2671
- if (isset($this->fonts[$fontName])) {
2672
- $this->numObj++;
2673
- $this->numFonts++;
2674
-
2675
- $font = &$this->fonts[$fontName];
2676
-
2677
- $name = basename($fontName);
2678
- $dir = dirname($fontName) . '/';
2679
- $options = array('name' => $name, 'fontFileName' => $fontName);
2680
-
2681
- if (is_array($encoding)) {
2682
- // then encoding and differences might be set
2683
- if (isset($encoding['encoding'])) {
2684
- $options['encoding'] = $encoding['encoding'];
2685
- }
2686
-
2687
- if (isset($encoding['differences'])) {
2688
- $options['differences'] = $encoding['differences'];
2689
- }
2690
- } else {
2691
- if (mb_strlen($encoding, '8bit')) {
2692
- // then perhaps only the encoding has been set
2693
- $options['encoding'] = $encoding;
2694
- }
2695
- }
2696
-
2697
- $fontObj = $this->numObj;
2698
- $this->o_font($this->numObj, 'new', $options);
2699
- $font['fontNum'] = $this->numFonts;
2700
-
2701
- // if this is a '.afm' font, and there is a '.pfa' file to go with it (as there
2702
- // should be for all non-basic fonts), then load it into an object and put the
2703
- // references into the font object
2704
- $basefile = $fontName;
2705
-
2706
- $fbtype = '';
2707
- if (file_exists("$basefile.ttf")) {
2708
- $fbtype = 'ttf';
2709
- } elseif (file_exists("$basefile.TTF")) {
2710
- $fbtype = 'TTF';
2711
- } elseif (file_exists("$basefile.pfb")) {
2712
- $fbtype = 'pfb';
2713
- } elseif (file_exists("$basefile.PFB")) {
2714
- $fbtype = 'PFB';
2715
- }
2716
-
2717
- $fbfile = "$basefile.$fbtype";
2718
-
2719
- // $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
2720
- // $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
2721
- $this->addMessage('selectFont: checking for - ' . $fbfile);
2722
-
2723
- // OAR - I don't understand this old check
2724
- // if (substr($fontName, -4) === '.afm' && strlen($fbtype)) {
2725
- if ($fbtype) {
2726
- $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
2727
- // $fontObj = $this->numObj;
2728
- $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
2729
-
2730
- // find the array of font widths, and put that into an object.
2731
- $firstChar = -1;
2732
- $lastChar = 0;
2733
- $widths = array();
2734
- $cid_widths = array();
2735
-
2736
- foreach ($font['C'] as $num => $d) {
2737
- if (intval($num) > 0 || $num == '0') {
2738
- if (!$font['isUnicode']) {
2739
- // With Unicode, widths array isn't used
2740
- if ($lastChar > 0 && $num > $lastChar + 1) {
2741
- for ($i = $lastChar + 1; $i < $num; $i++) {
2742
- $widths[] = 0;
2743
- }
2744
- }
2745
- }
2746
-
2747
- $widths[] = $d;
2748
-
2749
- if ($font['isUnicode']) {
2750
- $cid_widths[$num] = $d;
2751
- }
2752
-
2753
- if ($firstChar == -1) {
2754
- $firstChar = $num;
2755
- }
2756
-
2757
- $lastChar = $num;
2758
- }
2759
- }
2760
-
2761
- // also need to adjust the widths for the differences array
2762
- if (isset($options['differences'])) {
2763
- foreach ($options['differences'] as $charNum => $charName) {
2764
- if ($charNum > $lastChar) {
2765
- if (!$font['isUnicode']) {
2766
- // With Unicode, widths array isn't used
2767
- for ($i = $lastChar + 1; $i <= $charNum; $i++) {
2768
- $widths[] = 0;
2769
- }
2770
- }
2771
-
2772
- $lastChar = $charNum;
2773
- }
2774
-
2775
- if (isset($font['C'][$charName])) {
2776
- $widths[$charNum - $firstChar] = $font['C'][$charName];
2777
- if ($font['isUnicode']) {
2778
- $cid_widths[$charName] = $font['C'][$charName];
2779
- }
2780
- }
2781
- }
2782
- }
2783
-
2784
- if ($font['isUnicode']) {
2785
- $font['CIDWidths'] = $cid_widths;
2786
- }
2787
-
2788
- $this->addMessage('selectFont: FirstChar = ' . $firstChar);
2789
- $this->addMessage('selectFont: LastChar = ' . $lastChar);
2790
-
2791
- $widthid = -1;
2792
-
2793
- if (!$font['isUnicode']) {
2794
- // With Unicode, widths array isn't used
2795
-
2796
- $this->numObj++;
2797
- $this->o_contents($this->numObj, 'new', 'raw');
2798
- $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
2799
- $widthid = $this->numObj;
2800
- }
2801
-
2802
- $missing_width = 500;
2803
- $stemV = 70;
2804
-
2805
- if (isset($font['MissingWidth'])) {
2806
- $missing_width = $font['MissingWidth'];
2807
- }
2808
- if (isset($font['StdVW'])) {
2809
- $stemV = $font['StdVW'];
2810
- } else {
2811
- if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
2812
- $stemV = 120;
2813
- }
2814
- }
2815
-
2816
- // load the pfb file, and put that into an object too.
2817
- // note that pdf supports only binary format type 1 font files, though there is a
2818
- // simple utility to convert them from pfa to pfb.
2819
- // FIXME: should we move font subset creation to CPDF::output? See notes in issue #750.
2820
- if (!$this->isUnicode || strtolower($fbtype) !== 'ttf' || empty($this->stringSubsets)) {
2821
- $data = file_get_contents($fbfile);
2822
- } else {
2823
- $this->stringSubsets[$fontName][] = 32; // Force space if not in yet
2824
-
2825
- $subset = $this->stringSubsets[$fontName];
2826
- sort($subset);
2827
-
2828
- // Load font
2829
- $font_obj = Font::load($fbfile);
2830
- $font_obj->parse();
2831
-
2832
- // Define subset
2833
- $font_obj->setSubset($subset);
2834
- $font_obj->reduce();
2835
-
2836
- // Write new font
2837
- $tmp_name = $this->tmp . "/" . basename($fbfile) . ".tmp." . uniqid();
2838
- $font_obj->open($tmp_name, BinaryStream::modeWrite);
2839
- $font_obj->encode(array("OS/2"));
2840
- $font_obj->close();
2841
-
2842
- // Parse the new font to get cid2gid and widths
2843
- $font_obj = Font::load($tmp_name);
2844
-
2845
- // Find Unicode char map table
2846
- $subtable = null;
2847
- foreach ($font_obj->getData("cmap", "subtables") as $_subtable) {
2848
- if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
2849
- $subtable = $_subtable;
2850
- break;
2851
- }
2852
- }
2853
-
2854
- if ($subtable) {
2855
- $glyphIndexArray = $subtable["glyphIndexArray"];
2856
- $hmtx = $font_obj->getData("hmtx");
2857
-
2858
- unset($glyphIndexArray[0xFFFF]);
2859
-
2860
- $cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00");
2861
- $font['CIDWidths'] = array();
2862
- foreach ($glyphIndexArray as $cid => $gid) {
2863
- if ($cid >= 0 && $cid < 0xFFFF && $gid) {
2864
- $cidtogid[$cid * 2] = chr($gid >> 8);
2865
- $cidtogid[$cid * 2 + 1] = chr($gid & 0xFF);
2866
- }
2867
-
2868
- $width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]);
2869
- $font['CIDWidths'][$cid] = $width;
2870
- }
2871
-
2872
- $font['CIDtoGID'] = base64_encode(gzcompress($cidtogid));
2873
- $font['CIDtoGID_Compressed'] = true;
2874
-
2875
- $data = file_get_contents($tmp_name);
2876
- } else {
2877
- $data = file_get_contents($fbfile);
2878
- }
2879
-
2880
- $font_obj->close();
2881
- unlink($tmp_name);
2882
- }
2883
-
2884
- // create the font descriptor
2885
- $this->numObj++;
2886
- $fontDescriptorId = $this->numObj;
2887
-
2888
- $this->numObj++;
2889
- $pfbid = $this->numObj;
2890
-
2891
- // determine flags (more than a little flakey, hopefully will not matter much)
2892
- $flags = 0;
2893
-
2894
- if ($font['ItalicAngle'] != 0) {
2895
- $flags += pow(2, 6);
2896
- }
2897
-
2898
- if ($font['IsFixedPitch'] === 'true') {
2899
- $flags += 1;
2900
- }
2901
-
2902
- $flags += pow(2, 5); // assume non-sybolic
2903
- $list = array(
2904
- 'Ascent' => 'Ascender',
2905
- 'CapHeight' => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
2906
- 'MissingWidth' => 'MissingWidth',
2907
- 'Descent' => 'Descender',
2908
- 'FontBBox' => 'FontBBox',
2909
- 'ItalicAngle' => 'ItalicAngle'
2910
- );
2911
- $fdopt = array(
2912
- 'Flags' => $flags,
2913
- 'FontName' => $adobeFontName,
2914
- 'StemV' => $stemV
2915
- );
2916
-
2917
- foreach ($list as $k => $v) {
2918
- if (isset($font[$v])) {
2919
- $fdopt[$k] = $font[$v];
2920
- }
2921
- }
2922
-
2923
- if (strtolower($fbtype) === 'pfb') {
2924
- $fdopt['FontFile'] = $pfbid;
2925
- } elseif (strtolower($fbtype) === 'ttf') {
2926
- $fdopt['FontFile2'] = $pfbid;
2927
- }
2928
-
2929
- $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
2930
-
2931
- // embed the font program
2932
- $this->o_contents($this->numObj, 'new');
2933
- $this->objects[$pfbid]['c'] .= $data;
2934
-
2935
- // determine the cruicial lengths within this file
2936
- if (strtolower($fbtype) === 'pfb') {
2937
- $l1 = strpos($data, 'eexec') + 6;
2938
- $l2 = strpos($data, '00000000') - $l1;
2939
- $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
2940
- $this->o_contents(
2941
- $this->numObj,
2942
- 'add',
2943
- array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3)
2944
- );
2945
- } elseif (strtolower($fbtype) == 'ttf') {
2946
- $l1 = mb_strlen($data, '8bit');
2947
- $this->o_contents($this->numObj, 'add', array('Length1' => $l1));
2948
- }
2949
-
2950
- // tell the font object about all this new stuff
2951
- $tmp = array(
2952
- 'BaseFont' => $adobeFontName,
2953
- 'MissingWidth' => $missing_width,
2954
- 'Widths' => $widthid,
2955
- 'FirstChar' => $firstChar,
2956
- 'LastChar' => $lastChar,
2957
- 'FontDescriptor' => $fontDescriptorId
2958
- );
2959
-
2960
- if (strtolower($fbtype) === 'ttf') {
2961
- $tmp['SubType'] = 'TrueType';
2962
- }
2963
-
2964
- $this->addMessage("adding extra info to font.($fontObj)");
2965
-
2966
- foreach ($tmp as $fk => $fv) {
2967
- $this->addMessage("$fk : $fv");
2968
- }
2969
-
2970
- $this->o_font($fontObj, 'add', $tmp);
2971
- } else {
2972
- $this->addMessage(
2973
- 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
2974
- );
2975
- }
2976
-
2977
- // also set the differences here, note that this means that these will take effect only the
2978
- //first time that a font is selected, else they are ignored
2979
- if (isset($options['differences'])) {
2980
- $font['differences'] = $options['differences'];
2981
- }
2982
- }
2983
- }
2984
-
2985
- if ($set && isset($this->fonts[$fontName])) {
2986
- // so if for some reason the font was not set in the last one then it will not be selected
2987
- $this->currentBaseFont = $fontName;
2988
-
2989
- // the next lines mean that if a new font is selected, then the current text state will be
2990
- // applied to it as well.
2991
- $this->currentFont = $this->currentBaseFont;
2992
- $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
2993
-
2994
- //$this->setCurrentFont();
2995
- }
2996
-
2997
- return $this->currentFontNum;
2998
- //return $this->numObj;
2999
- }
3000
-
3001
- /**
3002
- * sets up the current font, based on the font families, and the current text state
3003
- * note that this system is quite flexible, a bold-italic font can be completely different to a
3004
- * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3005
- * This function is to be called whenever the currentTextState is changed, it will update
3006
- * the currentFont setting to whatever the appropriate family one is.
3007
- * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3008
- * This function will change the currentFont to whatever it should be, but will not change the
3009
- * currentBaseFont.
3010
- */
3011
- private function setCurrentFont()
3012
- {
3013
- // if (strlen($this->currentBaseFont) == 0){
3014
- // // then assume an initial font
3015
- // $this->selectFont($this->defaultFont);
3016
- // }
3017
- // $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3018
- // if (strlen($this->currentTextState)
3019
- // && isset($this->fontFamilies[$cf])
3020
- // && isset($this->fontFamilies[$cf][$this->currentTextState])){
3021
- // // then we are in some state or another
3022
- // // and this font has a family, and the current setting exists within it
3023
- // // select the font, then return it
3024
- // $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3025
- // $this->selectFont($nf,'',0);
3026
- // $this->currentFont = $nf;
3027
- // $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3028
- // } else {
3029
- // // the this font must not have the right family member for the current state
3030
- // // simply assume the base font
3031
- $this->currentFont = $this->currentBaseFont;
3032
- $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3033
- // }
3034
- }
3035
-
3036
- /**
3037
- * function for the user to find out what the ID is of the first page that was created during
3038
- * startup - useful if they wish to add something to it later.
3039
- *
3040
- * @return int
3041
- */
3042
- function getFirstPageId()
3043
- {
3044
- return $this->firstPageId;
3045
- }
3046
-
3047
- /**
3048
- * add content to the currently active object
3049
- *
3050
- * @param $content
3051
- */
3052
- private function addContent($content)
3053
- {
3054
- $this->objects[$this->currentContents]['c'] .= $content;
3055
- }
3056
-
3057
- /**
3058
- * sets the color for fill operations
3059
- *
3060
- * @param $color
3061
- * @param bool $force
3062
- */
3063
- function setColor($color, $force = false)
3064
- {
3065
- $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null);
3066
-
3067
- if (!$force && $this->currentColor == $new_color) {
3068
- return;
3069
- }
3070
-
3071
- if (isset($new_color[3])) {
3072
- $this->currentColor = $new_color;
3073
- $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3074
- } else {
3075
- if (isset($new_color[2])) {
3076
- $this->currentColor = $new_color;
3077
- $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3078
- }
3079
- }
3080
- }
3081
-
3082
- /**
3083
- * sets the color for fill operations
3084
- *
3085
- * @param $fillRule
3086
- */
3087
- function setFillRule($fillRule)
3088
- {
3089
- if (!in_array($fillRule, array("nonzero", "evenodd"))) {
3090
- return;
3091
- }
3092
-
3093
- $this->fillRule = $fillRule;
3094
- }
3095
-
3096
- /**
3097
- * sets the color for stroke operations
3098
- *
3099
- * @param $color
3100
- * @param bool $force
3101
- */
3102
- function setStrokeColor($color, $force = false)
3103
- {
3104
- $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null);
3105
-
3106
- if (!$force && $this->currentStrokeColor == $new_color) {
3107
- return;
3108
- }
3109
-
3110
- if (isset($new_color[3])) {
3111
- $this->currentStrokeColor = $new_color;
3112
- $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3113
- } else {
3114
- if (isset($new_color[2])) {
3115
- $this->currentStrokeColor = $new_color;
3116
- $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3117
- }
3118
- }
3119
- }
3120
-
3121
- /**
3122
- * Set the graphics state for compositions
3123
- *
3124
- * @param $parameters
3125
- */
3126
- function setGraphicsState($parameters)
3127
- {
3128
- // Create a new graphics state object if necessary
3129
- if (($gstate = array_search($parameters, $this->gstates)) === false) {
3130
- $this->numObj++;
3131
- $this->o_extGState($this->numObj, 'new', $parameters);
3132
- $gstate = $this->numStates;
3133
- $this->gstates[$gstate] = $parameters;
3134
- }
3135
- $this->addContent("\n/GS$gstate gs");
3136
- }
3137
-
3138
- /**
3139
- * Set current blend mode & opacity for lines.
3140
- *
3141
- * Valid blend modes are:
3142
- *
3143
- * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3144
- * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3145
- * Exclusion
3146
- *
3147
- * @param string $mode the blend mode to use
3148
- * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
3149
- */
3150
- function setLineTransparency($mode, $opacity)
3151
- {
3152
- static $blend_modes = array(
3153
- "Normal",
3154
- "Multiply",
3155
- "Screen",
3156
- "Overlay",
3157
- "Darken",
3158
- "Lighten",
3159
- "ColorDogde",
3160
- "ColorBurn",
3161
- "HardLight",
3162
- "SoftLight",
3163
- "Difference",
3164
- "Exclusion"
3165
- );
3166
-
3167
- if (!in_array($mode, $blend_modes)) {
3168
- $mode = "Normal";
3169
- }
3170
-
3171
- // Only create a new graphics state if required
3172
- if ($mode === $this->currentLineTransparency["mode"] &&
3173
- $opacity == $this->currentLineTransparency["opacity"]
3174
- ) {
3175
- return;
3176
- }
3177
-
3178
- $this->currentLineTransparency["mode"] = $mode;
3179
- $this->currentLineTransparency["opacity"] = $opacity;
3180
-
3181
- $options = array(
3182
- "BM" => "/$mode",
3183
- "CA" => (float)$opacity
3184
- );
3185
-
3186
- $this->setGraphicsState($options);
3187
- }
3188
-
3189
- /**
3190
- * Set current blend mode & opacity for filled objects.
3191
- *
3192
- * Valid blend modes are:
3193
- *
3194
- * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3195
- * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3196
- * Exclusion
3197
- *
3198
- * @param string $mode the blend mode to use
3199
- * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
3200
- */
3201
- function setFillTransparency($mode, $opacity)
3202
- {
3203
- static $blend_modes = array(
3204
- "Normal",
3205
- "Multiply",
3206
- "Screen",
3207
- "Overlay",
3208
- "Darken",
3209
- "Lighten",
3210
- "ColorDogde",
3211
- "ColorBurn",
3212
- "HardLight",
3213
- "SoftLight",
3214
- "Difference",
3215
- "Exclusion"
3216
- );
3217
-
3218
- if (!in_array($mode, $blend_modes)) {
3219
- $mode = "Normal";
3220
- }
3221
-
3222
- if ($mode === $this->currentFillTransparency["mode"] &&
3223
- $opacity == $this->currentFillTransparency["opacity"]
3224
- ) {
3225
- return;
3226
- }
3227
-
3228
- $this->currentFillTransparency["mode"] = $mode;
3229
- $this->currentFillTransparency["opacity"] = $opacity;
3230
-
3231
- $options = array(
3232
- "BM" => "/$mode",
3233
- "ca" => (float)$opacity,
3234
- );
3235
-
3236
- $this->setGraphicsState($options);
3237
- }
3238
-
3239
- /**
3240
- * draw a line from one set of coordinates to another
3241
- *
3242
- * @param $x1
3243
- * @param $y1
3244
- * @param $x2
3245
- * @param $y2
3246
- * @param bool $stroke
3247
- */
3248
- function line($x1, $y1, $x2, $y2, $stroke = true)
3249
- {
3250
- $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3251
-
3252
- if ($stroke) {
3253
- $this->addContent(' S');
3254
- }
3255
- }
3256
-
3257
- /**
3258
- * draw a bezier curve based on 4 control points
3259
- *
3260
- * @param $x0
3261
- * @param $y0
3262
- * @param $x1
3263
- * @param $y1
3264
- * @param $x2
3265
- * @param $y2
3266
- * @param $x3
3267
- * @param $y3
3268
- */
3269
- function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3270
- {
3271
- // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3272
- // as the control points for the curve.
3273
- $this->addContent(
3274
- sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3275
- );
3276
- }
3277
-
3278
- /**
3279
- * draw a part of an ellipse
3280
- *
3281
- * @param $x0
3282
- * @param $y0
3283
- * @param $astart
3284
- * @param $afinish
3285
- * @param $r1
3286
- * @param int $r2
3287
- * @param int $angle
3288
- * @param int $nSeg
3289
- */
3290
- function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3291
- {
3292
- $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3293
- }
3294
-
3295
- /**
3296
- * draw a filled ellipse
3297
- *
3298
- * @param $x0
3299
- * @param $y0
3300
- * @param $r1
3301
- * @param int $r2
3302
- * @param int $angle
3303
- * @param int $nSeg
3304
- * @param int $astart
3305
- * @param int $afinish
3306
- */
3307
- function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3308
- {
3309
- $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3310
- }
3311
-
3312
- /**
3313
- * @param $x
3314
- * @param $y
3315
- */
3316
- function lineTo($x, $y)
3317
- {
3318
- $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3319
- }
3320
-
3321
- /**
3322
- * @param $x
3323
- * @param $y
3324
- */
3325
- function moveTo($x, $y)
3326
- {
3327
- $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3328
- }
3329
-
3330
- /**
3331
- * draw a bezier curve based on 4 control points
3332
- *
3333
- * @param $x1
3334
- * @param $y1
3335
- * @param $x2
3336
- * @param $y2
3337
- * @param $x3
3338
- * @param $y3
3339
- */
3340
- function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3341
- {
3342
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3343
- }
3344
-
3345
- /**
3346
- * draw a bezier curve based on 4 control points
3347
- */ function quadTo($cpx, $cpy, $x, $y)
3348
- {
3349
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3350
- }
3351
-
3352
- function closePath()
3353
- {
3354
- $this->addContent(' h');
3355
- }
3356
-
3357
- function endPath()
3358
- {
3359
- $this->addContent(' n');
3360
- }
3361
-
3362
- /**
3363
- * draw an ellipse
3364
- * note that the part and filled ellipse are just special cases of this function
3365
- *
3366
- * draws an ellipse in the current line style
3367
- * centered at $x0,$y0, radii $r1,$r2
3368
- * if $r2 is not set, then a circle is drawn
3369
- * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3370
- * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3371
- * pretty crappy shape at 2, as we are approximating with bezier curves.
3372
- *
3373
- * @param $x0
3374
- * @param $y0
3375
- * @param $r1
3376
- * @param int $r2
3377
- * @param int $angle
3378
- * @param int $nSeg
3379
- * @param int $astart
3380
- * @param int $afinish
3381
- * @param bool $close
3382
- * @param bool $fill
3383
- * @param bool $stroke
3384
- * @param bool $incomplete
3385
- */
3386
- function ellipse(
3387
- $x0,
3388
- $y0,
3389
- $r1,
3390
- $r2 = 0,
3391
- $angle = 0,
3392
- $nSeg = 8,
3393
- $astart = 0,
3394
- $afinish = 360,
3395
- $close = true,
3396
- $fill = false,
3397
- $stroke = true,
3398
- $incomplete = false
3399
- ) {
3400
- if ($r1 == 0) {
3401
- return;
3402
- }
3403
-
3404
- if ($r2 == 0) {
3405
- $r2 = $r1;
3406
- }
3407
-
3408
- if ($nSeg < 2) {
3409
- $nSeg = 2;
3410
- }
3411
-
3412
- $astart = deg2rad((float)$astart);
3413
- $afinish = deg2rad((float)$afinish);
3414
- $totalAngle = $afinish - $astart;
3415
-
3416
- $dt = $totalAngle / $nSeg;
3417
- $dtm = $dt / 3;
3418
-
3419
- if ($angle != 0) {
3420
- $a = -1 * deg2rad((float)$angle);
3421
-
3422
- $this->addContent(
3423
- sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
3424
- );
3425
-
3426
- $x0 = 0;
3427
- $y0 = 0;
3428
- }
3429
-
3430
- $t1 = $astart;
3431
- $a0 = $x0 + $r1 * cos($t1);
3432
- $b0 = $y0 + $r2 * sin($t1);
3433
- $c0 = -$r1 * sin($t1);
3434
- $d0 = $r2 * cos($t1);
3435
-
3436
- if (!$incomplete) {
3437
- $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
3438
- }
3439
-
3440
- for ($i = 1; $i <= $nSeg; $i++) {
3441
- // draw this bit of the total curve
3442
- $t1 = $i * $dt + $astart;
3443
- $a1 = $x0 + $r1 * cos($t1);
3444
- $b1 = $y0 + $r2 * sin($t1);
3445
- $c1 = -$r1 * sin($t1);
3446
- $d1 = $r2 * cos($t1);
3447
-
3448
- $this->addContent(
3449
- sprintf(
3450
- "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
3451
- ($a0 + $c0 * $dtm),
3452
- ($b0 + $d0 * $dtm),
3453
- ($a1 - $c1 * $dtm),
3454
- ($b1 - $d1 * $dtm),
3455
- $a1,
3456
- $b1
3457
- )
3458
- );
3459
-
3460
- $a0 = $a1;
3461
- $b0 = $b1;
3462
- $c0 = $c1;
3463
- $d0 = $d1;
3464
- }
3465
-
3466
- if (!$incomplete) {
3467
- if ($fill) {
3468
- $this->addContent(' f');
3469
- }
3470
-
3471
- if ($stroke) {
3472
- if ($close) {
3473
- $this->addContent(' s'); // small 's' signifies closing the path as well
3474
- } else {
3475
- $this->addContent(' S');
3476
- }
3477
- }
3478
- }
3479
-
3480
- if ($angle != 0) {
3481
- $this->addContent(' Q');
3482
- }
3483
- }
3484
-
3485
- /**
3486
- * this sets the line drawing style.
3487
- * width, is the thickness of the line in user units
3488
- * cap is the type of cap to put on the line, values can be 'butt','round','square'
3489
- * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
3490
- * end of the line.
3491
- * join can be 'miter', 'round', 'bevel'
3492
- * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
3493
- * on and off dashes.
3494
- * (2) represents 2 on, 2 off, 2 on , 2 off ...
3495
- * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
3496
- * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
3497
- *
3498
- * @param int $width
3499
- * @param string $cap
3500
- * @param string $join
3501
- * @param string $dash
3502
- * @param int $phase
3503
- */
3504
- function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
3505
- {
3506
- // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
3507
- $string = '';
3508
-
3509
- if ($width > 0) {
3510
- $string .= "$width w";
3511
- }
3512
-
3513
- $ca = array('butt' => 0, 'round' => 1, 'square' => 2);
3514
-
3515
- if (isset($ca[$cap])) {
3516
- $string .= " $ca[$cap] J";
3517
- }
3518
-
3519
- $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
3520
-
3521
- if (isset($ja[$join])) {
3522
- $string .= " $ja[$join] j";
3523
- }
3524
-
3525
- if (is_array($dash)) {
3526
- $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
3527
- }
3528
-
3529
- $this->currentLineStyle = $string;
3530
- $this->addContent("\n$string");
3531
- }
3532
-
3533
- /**
3534
- * draw a polygon, the syntax for this is similar to the GD polygon command
3535
- *
3536
- * @param $p
3537
- * @param $np
3538
- * @param bool $f
3539
- */
3540
- function polygon($p, $np, $f = false)
3541
- {
3542
- $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
3543
-
3544
- for ($i = 2; $i < $np * 2; $i = $i + 2) {
3545
- $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
3546
- }
3547
-
3548
- if ($f) {
3549
- $this->addContent(' f');
3550
- } else {
3551
- $this->addContent(' S');
3552
- }
3553
- }
3554
-
3555
- /**
3556
- * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3557
- * the coordinates of the upper-right corner
3558
- *
3559
- * @param $x1
3560
- * @param $y1
3561
- * @param $width
3562
- * @param $height
3563
- */
3564
- function filledRectangle($x1, $y1, $width, $height)
3565
- {
3566
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
3567
- }
3568
-
3569
- /**
3570
- * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3571
- * the coordinates of the upper-right corner
3572
- *
3573
- * @param $x1
3574
- * @param $y1
3575
- * @param $width
3576
- * @param $height
3577
- */
3578
- function rectangle($x1, $y1, $width, $height)
3579
- {
3580
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
3581
- }
3582
-
3583
- /**
3584
- * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3585
- * the coordinates of the upper-right corner
3586
- *
3587
- * @param $x1
3588
- * @param $y1
3589
- * @param $width
3590
- * @param $height
3591
- */
3592
- function rect($x1, $y1, $width, $height)
3593
- {
3594
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
3595
- }
3596
-
3597
- function stroke()
3598
- {
3599
- $this->addContent("\nS");
3600
- }
3601
-
3602
- function fill()
3603
- {
3604
- $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
3605
- }
3606
-
3607
- function fillStroke()
3608
- {
3609
- $this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : ""));
3610
- }
3611
-
3612
- /**
3613
- * save the current graphic state
3614
- */
3615
- function save()
3616
- {
3617
- // we must reset the color cache or it will keep bad colors after clipping
3618
- $this->currentColor = null;
3619
- $this->currentStrokeColor = null;
3620
- $this->addContent("\nq");
3621
- }
3622
-
3623
- /**
3624
- * restore the last graphic state
3625
- */
3626
- function restore()
3627
- {
3628
- // we must reset the color cache or it will keep bad colors after clipping
3629
- $this->currentColor = null;
3630
- $this->currentStrokeColor = null;
3631
- $this->addContent("\nQ");
3632
- }
3633
-
3634
- /**
3635
- * draw a clipping rectangle, all the elements added after this will be clipped
3636
- *
3637
- * @param $x1
3638
- * @param $y1
3639
- * @param $width
3640
- * @param $height
3641
- */
3642
- function clippingRectangle($x1, $y1, $width, $height)
3643
- {
3644
- $this->save();
3645
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
3646
- }
3647
-
3648
- /**
3649
- * draw a clipping rounded rectangle, all the elements added after this will be clipped
3650
- *
3651
- * @param $x1
3652
- * @param $y1
3653
- * @param $w
3654
- * @param $h
3655
- * @param $rTL
3656
- * @param $rTR
3657
- * @param $rBR
3658
- * @param $rBL
3659
- */
3660
- function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
3661
- {
3662
- $this->save();
3663
-
3664
- // start: top edge, left end
3665
- $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
3666
-
3667
- // line: bottom edge, left end
3668
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
3669
-
3670
- // curve: bottom-left corner
3671
- $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
3672
-
3673
- // line: right edge, bottom end
3674
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
3675
-
3676
- // curve: bottom-right corner
3677
- $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
3678
-
3679
- // line: right edge, top end
3680
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
3681
-
3682
- // curve: bottom-right corner
3683
- $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
3684
-
3685
- // line: bottom edge, right end
3686
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
3687
-
3688
- // curve: top-right corner
3689
- $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
3690
-
3691
- // line: top edge, left end
3692
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
3693
-
3694
- // Close & clip
3695
- $this->addContent(" W n");
3696
- }
3697
-
3698
- /**
3699
- * ends the last clipping shape
3700
- */
3701
- function clippingEnd()
3702
- {
3703
- $this->restore();
3704
- }
3705
-
3706
- /**
3707
- * scale
3708
- *
3709
- * @param float $s_x scaling factor for width as percent
3710
- * @param float $s_y scaling factor for height as percent
3711
- * @param float $x Origin abscissa
3712
- * @param float $y Origin ordinate
3713
- */
3714
- function scale($s_x, $s_y, $x, $y)
3715
- {
3716
- $y = $this->currentPageSize["height"] - $y;
3717
-
3718
- $tm = array(
3719
- $s_x,
3720
- 0,
3721
- 0,
3722
- $s_y,
3723
- $x * (1 - $s_x),
3724
- $y * (1 - $s_y)
3725
- );
3726
-
3727
- $this->transform($tm);
3728
- }
3729
-
3730
- /**
3731
- * translate
3732
- *
3733
- * @param float $t_x movement to the right
3734
- * @param float $t_y movement to the bottom
3735
- */
3736
- function translate($t_x, $t_y)
3737
- {
3738
- $tm = array(
3739
- 1,
3740
- 0,
3741
- 0,
3742
- 1,
3743
- $t_x,
3744
- -$t_y
3745
- );
3746
-
3747
- $this->transform($tm);
3748
- }
3749
-
3750
- /**
3751
- * rotate
3752
- *
3753
- * @param float $angle angle in degrees for counter-clockwise rotation
3754
- * @param float $x Origin abscissa
3755
- * @param float $y Origin ordinate
3756
- */
3757
- function rotate($angle, $x, $y)
3758
- {
3759
- $y = $this->currentPageSize["height"] - $y;
3760
-
3761
- $a = deg2rad($angle);
3762
- $cos_a = cos($a);
3763
- $sin_a = sin($a);
3764
-
3765
- $tm = array(
3766
- $cos_a,
3767
- -$sin_a,
3768
- $sin_a,
3769
- $cos_a,
3770
- $x - $sin_a * $y - $cos_a * $x,
3771
- $y - $cos_a * $y + $sin_a * $x,
3772
- );
3773
-
3774
- $this->transform($tm);
3775
- }
3776
-
3777
- /**
3778
- * skew
3779
- *
3780
- * @param float $angle_x
3781
- * @param float $angle_y
3782
- * @param float $x Origin abscissa
3783
- * @param float $y Origin ordinate
3784
- */
3785
- function skew($angle_x, $angle_y, $x, $y)
3786
- {
3787
- $y = $this->currentPageSize["height"] - $y;
3788
-
3789
- $tan_x = tan(deg2rad($angle_x));
3790
- $tan_y = tan(deg2rad($angle_y));
3791
-
3792
- $tm = array(
3793
- 1,
3794
- -$tan_y,
3795
- -$tan_x,
3796
- 1,
3797
- $tan_x * $y,
3798
- $tan_y * $x,
3799
- );
3800
-
3801
- $this->transform($tm);
3802
- }
3803
-
3804
- /**
3805
- * apply graphic transformations
3806
- *
3807
- * @param array $tm transformation matrix
3808
- */
3809
- function transform($tm)
3810
- {
3811
- $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
3812
- }
3813
-
3814
- /**
3815
- * add a new page to the document
3816
- * this also makes the new page the current active object
3817
- *
3818
- * @param int $insert
3819
- * @param int $id
3820
- * @param string $pos
3821
- * @return int
3822
- */
3823
- function newPage($insert = 0, $id = 0, $pos = 'after')
3824
- {
3825
- // if there is a state saved, then go up the stack closing them
3826
- // then on the new page, re-open them with the right setings
3827
-
3828
- if ($this->nStateStack) {
3829
- for ($i = $this->nStateStack; $i >= 1; $i--) {
3830
- $this->restoreState($i);
3831
- }
3832
- }
3833
-
3834
- $this->numObj++;
3835
-
3836
- if ($insert) {
3837
- // the id from the ezPdf class is the id of the contents of the page, not the page object itself
3838
- // query that object to find the parent
3839
- $rid = $this->objects[$id]['onPage'];
3840
- $opt = array('rid' => $rid, 'pos' => $pos);
3841
- $this->o_page($this->numObj, 'new', $opt);
3842
- } else {
3843
- $this->o_page($this->numObj, 'new');
3844
- }
3845
-
3846
- // if there is a stack saved, then put that onto the page
3847
- if ($this->nStateStack) {
3848
- for ($i = 1; $i <= $this->nStateStack; $i++) {
3849
- $this->saveState($i);
3850
- }
3851
- }
3852
-
3853
- // and if there has been a stroke or fill color set, then transfer them
3854
- if (isset($this->currentColor)) {
3855
- $this->setColor($this->currentColor, true);
3856
- }
3857
-
3858
- if (isset($this->currentStrokeColor)) {
3859
- $this->setStrokeColor($this->currentStrokeColor, true);
3860
- }
3861
-
3862
- // if there is a line style set, then put this in too
3863
- if (mb_strlen($this->currentLineStyle, '8bit')) {
3864
- $this->addContent("\n$this->currentLineStyle");
3865
- }
3866
-
3867
- // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
3868
- return $this->currentContents;
3869
- }
3870
-
3871
- /**
3872
- * Streams the PDF to the client.
3873
- *
3874
- * @param string $filename The filename to present to the client.
3875
- * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
3876
- */
3877
- function stream($filename = "document.pdf", $options = array())
3878
- {
3879
- if (headers_sent()) {
3880
- die("Unable to stream pdf: headers already sent");
3881
- }
3882
-
3883
- if (!isset($options["compress"])) $options["compress"] = true;
3884
- if (!isset($options["Attachment"])) $options["Attachment"] = true;
3885
-
3886
- $debug = !$options['compress'];
3887
- $tmp = ltrim($this->output($debug));
3888
-
3889
- header("Cache-Control: private");
3890
- header("Content-Type: application/pdf");
3891
- header("Content-Length: " . mb_strlen($tmp, "8bit"));
3892
-
3893
- $filename = str_replace(array("\n", "'"), "", basename($filename, ".pdf")) . ".pdf";
3894
- $attachment = $options["Attachment"] ? "attachment" : "inline";
3895
-
3896
- $encoding = mb_detect_encoding($filename);
3897
- $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
3898
- $fallbackfilename = str_replace("\"", "", $fallbackfilename);
3899
- $encodedfilename = rawurlencode($filename);
3900
-
3901
- $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
3902
- if ($fallbackfilename !== $filename) {
3903
- $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
3904
- }
3905
- header($contentDisposition);
3906
-
3907
- echo $tmp;
3908
- flush();
3909
- }
3910
-
3911
- /**
3912
- * return the height in units of the current font in the given size
3913
- *
3914
- * @param $size
3915
- * @return float|int
3916
- */
3917
- function getFontHeight($size)
3918
- {
3919
- if (!$this->numFonts) {
3920
- $this->selectFont($this->defaultFont);
3921
- }
3922
-
3923
- $font = $this->fonts[$this->currentFont];
3924
-
3925
- // for the current font, and the given size, what is the height of the font in user units
3926
- if (isset($font['Ascender']) && isset($font['Descender'])) {
3927
- $h = $font['Ascender'] - $font['Descender'];
3928
- } else {
3929
- $h = $font['FontBBox'][3] - $font['FontBBox'][1];
3930
- }
3931
-
3932
- // have to adjust by a font offset for Windows fonts. unfortunately it looks like
3933
- // the bounding box calculations are wrong and I don't know why.
3934
- if (isset($font['FontHeightOffset'])) {
3935
- // For CourierNew from Windows this needs to be -646 to match the
3936
- // Adobe native Courier font.
3937
- //
3938
- // For FreeMono from GNU this needs to be -337 to match the
3939
- // Courier font.
3940
- //
3941
- // Both have been added manually to the .afm and .ufm files.
3942
- $h += (int)$font['FontHeightOffset'];
3943
- }
3944
-
3945
- return $size * $h / 1000;
3946
- }
3947
-
3948
- /**
3949
- * @param $size
3950
- * @return float|int
3951
- */
3952
- function getFontXHeight($size)
3953
- {
3954
- if (!$this->numFonts) {
3955
- $this->selectFont($this->defaultFont);
3956
- }
3957
-
3958
- $font = $this->fonts[$this->currentFont];
3959
-
3960
- // for the current font, and the given size, what is the height of the font in user units
3961
- if (isset($font['XHeight'])) {
3962
- $xh = $font['Ascender'] - $font['Descender'];
3963
- } else {
3964
- $xh = $this->getFontHeight($size) / 2;
3965
- }
3966
-
3967
- return $size * $xh / 1000;
3968
- }
3969
-
3970
- /**
3971
- * return the font descender, this will normally return a negative number
3972
- * if you add this number to the baseline, you get the level of the bottom of the font
3973
- * it is in the pdf user units
3974
- *
3975
- * @param $size
3976
- * @return float|int
3977
- */
3978
- function getFontDescender($size)
3979
- {
3980
- // note that this will most likely return a negative value
3981
- if (!$this->numFonts) {
3982
- $this->selectFont($this->defaultFont);
3983
- }
3984
-
3985
- //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
3986
- $h = $this->fonts[$this->currentFont]['Descender'];
3987
-
3988
- return $size * $h / 1000;
3989
- }
3990
-
3991
- /**
3992
- * filter the text, this is applied to all text just before being inserted into the pdf document
3993
- * it escapes the various things that need to be escaped, and so on
3994
- *
3995
- * @access private
3996
- *
3997
- * @param $text
3998
- * @param bool $bom
3999
- * @param bool $convert_encoding
4000
- * @return string
4001
- */
4002
- function filterText($text, $bom = true, $convert_encoding = true)
4003
- {
4004
- if (!$this->numFonts) {
4005
- $this->selectFont($this->defaultFont);
4006
- }
4007
-
4008
- if ($convert_encoding) {
4009
- $cf = $this->currentFont;
4010
- if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4011
- $text = $this->utf8toUtf16BE($text, $bom);
4012
- } else {
4013
- //$text = html_entity_decode($text, ENT_QUOTES);
4014
- $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4015
- }
4016
- } else if ($bom) {
4017
- $text = $this->utf8toUtf16BE($text, $bom);
4018
- }
4019
-
4020
- // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4021
- return strtr($text, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
4022
- }
4023
-
4024
- /**
4025
- * return array containing codepoints (UTF-8 character values) for the
4026
- * string passed in.
4027
- *
4028
- * based on the excellent TCPDF code by Nicola Asuni and the
4029
- * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4030
- *
4031
- * @access private
4032
- * @author Orion Richardson
4033
- * @since January 5, 2008
4034
- *
4035
- * @param string $text UTF-8 string to process
4036
- *
4037
- * @return array UTF-8 codepoints array for the string
4038
- */
4039
- function utf8toCodePointsArray(&$text)
4040
- {
4041
- $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4042
- $unicode = array(); // array containing unicode values
4043
- $bytes = array(); // array containing single character byte sequences
4044
- $numbytes = 1; // number of octets needed to represent the UTF-8 character
4045
-
4046
- for ($i = 0; $i < $length; $i++) {
4047
- $c = ord($text[$i]); // get one string character at time
4048
- if (count($bytes) === 0) { // get starting octect
4049
- if ($c <= 0x7F) {
4050
- $unicode[] = $c; // use the character "as is" because is ASCII
4051
- $numbytes = 1;
4052
- } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4053
- $bytes[] = ($c - 0xC0) << 0x06;
4054
- $numbytes = 2;
4055
- } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4056
- $bytes[] = ($c - 0xE0) << 0x0C;
4057
- $numbytes = 3;
4058
- } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4059
- $bytes[] = ($c - 0xF0) << 0x12;
4060
- $numbytes = 4;
4061
- } else {
4062
- // use replacement character for other invalid sequences
4063
- $unicode[] = 0xFFFD;
4064
- $bytes = array();
4065
- $numbytes = 1;
4066
- }
4067
- } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4068
- $bytes[] = $c - 0x80;
4069
- if (count($bytes) === $numbytes) {
4070
- // compose UTF-8 bytes to a single unicode value
4071
- $c = $bytes[0];
4072
- for ($j = 1; $j < $numbytes; $j++) {
4073
- $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4074
- }
4075
- if ((($c >= 0xD800) AND ($c <= 0xDFFF)) OR ($c >= 0x10FFFF)) {
4076
- // The definition of UTF-8 prohibits encoding character numbers between
4077
- // U+D800 and U+DFFF, which are reserved for use with the UTF-16
4078
- // encoding form (as surrogate pairs) and do not directly represent
4079
- // characters.
4080
- $unicode[] = 0xFFFD; // use replacement character
4081
- } else {
4082
- $unicode[] = $c; // add char to array
4083
- }
4084
- // reset data for next char
4085
- $bytes = array();
4086
- $numbytes = 1;
4087
- }
4088
- } else {
4089
- // use replacement character for other invalid sequences
4090
- $unicode[] = 0xFFFD;
4091
- $bytes = array();
4092
- $numbytes = 1;
4093
- }
4094
- }
4095
-
4096
- return $unicode;
4097
- }
4098
-
4099
- /**
4100
- * convert UTF-8 to UTF-16 with an additional byte order marker
4101
- * at the front if required.
4102
- *
4103
- * based on the excellent TCPDF code by Nicola Asuni and the
4104
- * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4105
- *
4106
- * @access private
4107
- * @author Orion Richardson
4108
- * @since January 5, 2008
4109
- *
4110
- * @param string $text UTF-8 string to process
4111
- * @param boolean $bom whether to add the byte order marker
4112
- *
4113
- * @return string UTF-16 result string
4114
- */
4115
- function utf8toUtf16BE(&$text, $bom = true)
4116
- {
4117
- $out = $bom ? "\xFE\xFF" : '';
4118
-
4119
- $unicode = $this->utf8toCodePointsArray($text);
4120
- foreach ($unicode as $c) {
4121
- if ($c === 0xFFFD) {
4122
- $out .= "\xFF\xFD"; // replacement character
4123
- } elseif ($c < 0x10000) {
4124
- $out .= chr($c >> 0x08) . chr($c & 0xFF);
4125
- } else {
4126
- $c -= 0x10000;
4127
- $w1 = 0xD800 | ($c >> 0x10);
4128
- $w2 = 0xDC00 | ($c & 0x3FF);
4129
- $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4130
- }
4131
- }
4132
-
4133
- return $out;
4134
- }
4135
-
4136
- /**
4137
- * given a start position and information about how text is to be laid out, calculate where
4138
- * on the page the text will end
4139
- *
4140
- * @param $x
4141
- * @param $y
4142
- * @param $angle
4143
- * @param $size
4144
- * @param $wa
4145
- * @param $text
4146
- * @return array
4147
- */
4148
- private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4149
- {
4150
- // given this information return an array containing x and y for the end position as elements 0 and 1
4151
- $w = $this->getTextWidth($size, $text);
4152
-
4153
- // need to adjust for the number of spaces in this text
4154
- $words = explode(' ', $text);
4155
- $nspaces = count($words) - 1;
4156
- $w += $wa * $nspaces;
4157
- $a = deg2rad((float)$angle);
4158
-
4159
- return array(cos($a) * $w + $x, -sin($a) * $w + $y);
4160
- }
4161
-
4162
- /**
4163
- * Callback method used by smallCaps
4164
- *
4165
- * @param array $matches
4166
- *
4167
- * @return string
4168
- */
4169
- function toUpper($matches)
4170
- {
4171
- return mb_strtoupper($matches[0]);
4172
- }
4173
-
4174
- function concatMatches($matches)
4175
- {
4176
- $str = "";
4177
- foreach ($matches as $match) {
4178
- $str .= $match[0];
4179
- }
4180
-
4181
- return $str;
4182
- }
4183
-
4184
- /**
4185
- * register text for font subsetting
4186
- *
4187
- * @param $font
4188
- * @param $text
4189
- */
4190
- function registerText($font, $text)
4191
- {
4192
- if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4193
- return;
4194
- }
4195
-
4196
- if (!isset($this->stringSubsets[$font])) {
4197
- $this->stringSubsets[$font] = array();
4198
- }
4199
-
4200
- $this->stringSubsets[$font] = array_unique(
4201
- array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
4202
- );
4203
- }
4204
-
4205
- /**
4206
- * add text to the document, at a specified location, size and angle on the page
4207
- *
4208
- * @param $x
4209
- * @param $y
4210
- * @param $size
4211
- * @param $text
4212
- * @param int $angle
4213
- * @param int $wordSpaceAdjust
4214
- * @param int $charSpaceAdjust
4215
- * @param bool $smallCaps
4216
- */
4217
- function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4218
- {
4219
- if (!$this->numFonts) {
4220
- $this->selectFont($this->defaultFont);
4221
- }
4222
-
4223
- $text = str_replace(array("\r", "\n"), "", $text);
4224
-
4225
- if ($smallCaps) {
4226
- preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4227
- $lower = $this->concatMatches($matches);
4228
- d($lower);
4229
-
4230
- preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4231
- $other = $this->concatMatches($matches);
4232
- d($other);
4233
-
4234
- //$text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
4235
- }
4236
-
4237
- // if there are any open callbacks, then they should be called, to show the start of the line
4238
- if ($this->nCallback > 0) {
4239
- for ($i = $this->nCallback; $i > 0; $i--) {
4240
- // call each function
4241
- $info = array(
4242
- 'x' => $x,
4243
- 'y' => $y,
4244
- 'angle' => $angle,
4245
- 'status' => 'sol',
4246
- 'p' => $this->callback[$i]['p'],
4247
- 'nCallback' => $this->callback[$i]['nCallback'],
4248
- 'height' => $this->callback[$i]['height'],
4249
- 'descender' => $this->callback[$i]['descender']
4250
- );
4251
-
4252
- $func = $this->callback[$i]['f'];
4253
- $this->$func($info);
4254
- }
4255
- }
4256
-
4257
- if ($angle == 0) {
4258
- $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
4259
- } else {
4260
- $a = deg2rad((float)$angle);
4261
- $this->addContent(
4262
- sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
4263
- );
4264
- }
4265
-
4266
- if ($wordSpaceAdjust != 0) {
4267
- $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
4268
- }
4269
-
4270
- if ($charSpaceAdjust != 0) {
4271
- $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
4272
- }
4273
-
4274
- $len = mb_strlen($text);
4275
- $start = 0;
4276
-
4277
- if ($start < $len) {
4278
- $part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start);
4279
- $place_text = $this->filterText($part, false);
4280
- // modify unicode text so that extra word spacing is manually implemented (bug #)
4281
- $cf = $this->currentFont;
4282
- if ($this->fonts[$cf]['isUnicode'] && $wordSpaceAdjust != 0) {
4283
- $space_scale = 1000 / $size;
4284
- $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
4285
- }
4286
- $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
4287
- $this->addContent(" [($place_text)] TJ");
4288
- }
4289
-
4290
- if ($wordSpaceAdjust != 0) {
4291
- $this->addContent(sprintf(" %.3F Tw", 0));
4292
- }
4293
-
4294
- if ($charSpaceAdjust != 0) {
4295
- $this->addContent(sprintf(" %.3F Tc", 0));
4296
- }
4297
-
4298
- $this->addContent(' ET');
4299
-
4300
- // if there are any open callbacks, then they should be called, to show the end of the line
4301
- if ($this->nCallback > 0) {
4302
- for ($i = $this->nCallback; $i > 0; $i--) {
4303
- // call each function
4304
- $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
4305
- $info = array(
4306
- 'x' => $tmp[0],
4307
- 'y' => $tmp[1],
4308
- 'angle' => $angle,
4309
- 'status' => 'eol',
4310
- 'p' => $this->callback[$i]['p'],
4311
- 'nCallback' => $this->callback[$i]['nCallback'],
4312
- 'height' => $this->callback[$i]['height'],
4313
- 'descender' => $this->callback[$i]['descender']
4314
- );
4315
- $func = $this->callback[$i]['f'];
4316
- $this->$func($info);
4317
- }
4318
- }
4319
- }
4320
-
4321
- /**
4322
- * calculate how wide a given text string will be on a page, at a given size.
4323
- * this can be called externally, but is also used by the other class functions
4324
- *
4325
- * @param $size
4326
- * @param $text
4327
- * @param int $word_spacing
4328
- * @param int $char_spacing
4329
- * @return float|int
4330
- */
4331
- function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0)
4332
- {
4333
- static $ord_cache = array();
4334
-
4335
- // this function should not change any of the settings, though it will need to
4336
- // track any directives which change during calculation, so copy them at the start
4337
- // and put them back at the end.
4338
- $store_currentTextState = $this->currentTextState;
4339
-
4340
- if (!$this->numFonts) {
4341
- $this->selectFont($this->defaultFont);
4342
- }
4343
-
4344
- $text = str_replace(array("\r", "\n"), "", $text);
4345
-
4346
- // converts a number or a float to a string so it can get the width
4347
- $text = "$text";
4348
-
4349
- // hmm, this is where it all starts to get tricky - use the font information to
4350
- // calculate the width of each character, add them up and convert to user units
4351
- $w = 0;
4352
- $cf = $this->currentFont;
4353
- $current_font = $this->fonts[$cf];
4354
- $space_scale = 1000 / ($size > 0 ? $size : 1);
4355
- $n_spaces = 0;
4356
-
4357
- if ($current_font['isUnicode']) {
4358
- // for Unicode, use the code points array to calculate width rather
4359
- // than just the string itself
4360
- $unicode = $this->utf8toCodePointsArray($text);
4361
-
4362
- foreach ($unicode as $char) {
4363
- // check if we have to replace character
4364
- if (isset($current_font['differences'][$char])) {
4365
- $char = $current_font['differences'][$char];
4366
- }
4367
-
4368
- if (isset($current_font['C'][$char])) {
4369
- $char_width = $current_font['C'][$char];
4370
-
4371
- // add the character width
4372
- $w += $char_width;
4373
-
4374
- // add additional padding for space
4375
- if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
4376
- $w += $word_spacing * $space_scale;
4377
- $n_spaces++;
4378
- }
4379
- }
4380
- }
4381
-
4382
- // add additional char spacing
4383
- if ($char_spacing != 0) {
4384
- $w += $char_spacing * $space_scale * (count($unicode) + $n_spaces);
4385
- }
4386
-
4387
- } else {
4388
- // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
4389
- if ($this->isUnicode) {
4390
- $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
4391
- }
4392
-
4393
- $len = mb_strlen($text, 'Windows-1252');
4394
-
4395
- for ($i = 0; $i < $len; $i++) {
4396
- $c = $text[$i];
4397
- $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
4398
-
4399
- // check if we have to replace character
4400
- if (isset($current_font['differences'][$char])) {
4401
- $char = $current_font['differences'][$char];
4402
- }
4403
-
4404
- if (isset($current_font['C'][$char])) {
4405
- $char_width = $current_font['C'][$char];
4406
-
4407
- // add the character width
4408
- $w += $char_width;
4409
-
4410
- // add additional padding for space
4411
- if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
4412
- $w += $word_spacing * $space_scale;
4413
- $n_spaces++;
4414
- }
4415
- }
4416
- }
4417
-
4418
- // add additional char spacing
4419
- if ($char_spacing != 0) {
4420
- $w += $char_spacing * $space_scale * ($len + $n_spaces);
4421
- }
4422
- }
4423
-
4424
- $this->currentTextState = $store_currentTextState;
4425
- $this->setCurrentFont();
4426
-
4427
- return $w * $size / 1000;
4428
- }
4429
-
4430
- /**
4431
- * this will be called at a new page to return the state to what it was on the
4432
- * end of the previous page, before the stack was closed down
4433
- * This is to get around not being able to have open 'q' across pages
4434
- *
4435
- * @param int $pageEnd
4436
- */
4437
- function saveState($pageEnd = 0)
4438
- {
4439
- if ($pageEnd) {
4440
- // this will be called at a new page to return the state to what it was on the
4441
- // end of the previous page, before the stack was closed down
4442
- // This is to get around not being able to have open 'q' across pages
4443
- $opt = $this->stateStack[$pageEnd];
4444
- // ok to use this as stack starts numbering at 1
4445
- $this->setColor($opt['col'], true);
4446
- $this->setStrokeColor($opt['str'], true);
4447
- $this->addContent("\n" . $opt['lin']);
4448
- // $this->currentLineStyle = $opt['lin'];
4449
- } else {
4450
- $this->nStateStack++;
4451
- $this->stateStack[$this->nStateStack] = array(
4452
- 'col' => $this->currentColor,
4453
- 'str' => $this->currentStrokeColor,
4454
- 'lin' => $this->currentLineStyle
4455
- );
4456
- }
4457
-
4458
- $this->save();
4459
- }
4460
-
4461
- /**
4462
- * restore a previously saved state
4463
- *
4464
- * @param int $pageEnd
4465
- */
4466
- function restoreState($pageEnd = 0)
4467
- {
4468
- if (!$pageEnd) {
4469
- $n = $this->nStateStack;
4470
- $this->currentColor = $this->stateStack[$n]['col'];
4471
- $this->currentStrokeColor = $this->stateStack[$n]['str'];
4472
- $this->addContent("\n" . $this->stateStack[$n]['lin']);
4473
- $this->currentLineStyle = $this->stateStack[$n]['lin'];
4474
- $this->stateStack[$n] = null;
4475
- unset($this->stateStack[$n]);
4476
- $this->nStateStack--;
4477
- }
4478
-
4479
- $this->restore();
4480
- }
4481
-
4482
- /**
4483
- * make a loose object, the output will go into this object, until it is closed, then will revert to
4484
- * the current one.
4485
- * this object will not appear until it is included within a page.
4486
- * the function will return the object number
4487
- *
4488
- * @return int
4489
- */
4490
- function openObject()
4491
- {
4492
- $this->nStack++;
4493
- $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
4494
- // add a new object of the content type, to hold the data flow
4495
- $this->numObj++;
4496
- $this->o_contents($this->numObj, 'new');
4497
- $this->currentContents = $this->numObj;
4498
- $this->looseObjects[$this->numObj] = 1;
4499
-
4500
- return $this->numObj;
4501
- }
4502
-
4503
- /**
4504
- * open an existing object for editing
4505
- *
4506
- * @param $id
4507
- */
4508
- function reopenObject($id)
4509
- {
4510
- $this->nStack++;
4511
- $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
4512
- $this->currentContents = $id;
4513
-
4514
- // also if this object is the primary contents for a page, then set the current page to its parent
4515
- if (isset($this->objects[$id]['onPage'])) {
4516
- $this->currentPage = $this->objects[$id]['onPage'];
4517
- }
4518
- }
4519
-
4520
- /**
4521
- * close an object
4522
- */
4523
- function closeObject()
4524
- {
4525
- // close the object, as long as there was one open in the first place, which will be indicated by
4526
- // an objectId on the stack.
4527
- if ($this->nStack > 0) {
4528
- $this->currentContents = $this->stack[$this->nStack]['c'];
4529
- $this->currentPage = $this->stack[$this->nStack]['p'];
4530
- $this->nStack--;
4531
- // easier to probably not worry about removing the old entries, they will be overwritten
4532
- // if there are new ones.
4533
- }
4534
- }
4535
-
4536
- /**
4537
- * stop an object from appearing on pages from this point on
4538
- *
4539
- * @param $id
4540
- */
4541
- function stopObject($id)
4542
- {
4543
- // if an object has been appearing on pages up to now, then stop it, this page will
4544
- // be the last one that could contain it.
4545
- if (isset($this->addLooseObjects[$id])) {
4546
- $this->addLooseObjects[$id] = '';
4547
- }
4548
- }
4549
-
4550
- /**
4551
- * after an object has been created, it wil only show if it has been added, using this function.
4552
- *
4553
- * @param $id
4554
- * @param string $options
4555
- */
4556
- function addObject($id, $options = 'add')
4557
- {
4558
- // add the specified object to the page
4559
- if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
4560
- // then it is a valid object, and it is not being added to itself
4561
- switch ($options) {
4562
- case 'all':
4563
- // then this object is to be added to this page (done in the next block) and
4564
- // all future new pages.
4565
- $this->addLooseObjects[$id] = 'all';
4566
-
4567
- case 'add':
4568
- if (isset($this->objects[$this->currentContents]['onPage'])) {
4569
- // then the destination contents is the primary for the page
4570
- // (though this object is actually added to that page)
4571
- $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
4572
- }
4573
- break;
4574
-
4575
- case 'even':
4576
- $this->addLooseObjects[$id] = 'even';
4577
- $pageObjectId = $this->objects[$this->currentContents]['onPage'];
4578
- if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
4579
- $this->addObject($id);
4580
- // hacky huh :)
4581
- }
4582
- break;
4583
-
4584
- case 'odd':
4585
- $this->addLooseObjects[$id] = 'odd';
4586
- $pageObjectId = $this->objects[$this->currentContents]['onPage'];
4587
- if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
4588
- $this->addObject($id);
4589
- // hacky huh :)
4590
- }
4591
- break;
4592
-
4593
- case 'next':
4594
- $this->addLooseObjects[$id] = 'all';
4595
- break;
4596
-
4597
- case 'nexteven':
4598
- $this->addLooseObjects[$id] = 'even';
4599
- break;
4600
-
4601
- case 'nextodd':
4602
- $this->addLooseObjects[$id] = 'odd';
4603
- break;
4604
- }
4605
- }
4606
- }
4607
-
4608
- /**
4609
- * return a storable representation of a specific object
4610
- *
4611
- * @param $id
4612
- * @return string|null
4613
- */
4614
- function serializeObject($id)
4615
- {
4616
- if (array_key_exists($id, $this->objects)) {
4617
- return serialize($this->objects[$id]);
4618
- }
4619
-
4620
- return null;
4621
- }
4622
-
4623
- /**
4624
- * restore an object from its stored representation. returns its new object id.
4625
- *
4626
- * @param $obj
4627
- * @return int
4628
- */
4629
- function restoreSerializedObject($obj)
4630
- {
4631
- $obj_id = $this->openObject();
4632
- $this->objects[$obj_id] = unserialize($obj);
4633
- $this->closeObject();
4634
-
4635
- return $obj_id;
4636
- }
4637
-
4638
- /**
4639
- * add content to the documents info object
4640
- *
4641
- * @param $label
4642
- * @param int $value
4643
- */
4644
- function addInfo($label, $value = 0)
4645
- {
4646
- // this will only work if the label is one of the valid ones.
4647
- // modify this so that arrays can be passed as well.
4648
- // if $label is an array then assume that it is key => value pairs
4649
- // else assume that they are both scalar, anything else will probably error
4650
- if (is_array($label)) {
4651
- foreach ($label as $l => $v) {
4652
- $this->o_info($this->infoObject, $l, $v);
4653
- }
4654
- } else {
4655
- $this->o_info($this->infoObject, $label, $value);
4656
- }
4657
- }
4658
-
4659
- /**
4660
- * set the viewer preferences of the document, it is up to the browser to obey these.
4661
- *
4662
- * @param $label
4663
- * @param int $value
4664
- */
4665
- function setPreferences($label, $value = 0)
4666
- {
4667
- // this will only work if the label is one of the valid ones.
4668
- if (is_array($label)) {
4669
- foreach ($label as $l => $v) {
4670
- $this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v));
4671
- }
4672
- } else {
4673
- $this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value));
4674
- }
4675
- }
4676
-
4677
- /**
4678
- * extract an integer from a position in a byte stream
4679
- *
4680
- * @param $data
4681
- * @param $pos
4682
- * @param $num
4683
- * @return int
4684
- */
4685
- private function getBytes(&$data, $pos, $num)
4686
- {
4687
- // return the integer represented by $num bytes from $pos within $data
4688
- $ret = 0;
4689
- for ($i = 0; $i < $num; $i++) {
4690
- $ret *= 256;
4691
- $ret += ord($data[$pos + $i]);
4692
- }
4693
-
4694
- return $ret;
4695
- }
4696
-
4697
- /**
4698
- * Check if image already added to pdf image directory.
4699
- * If yes, need not to create again (pass empty data)
4700
- *
4701
- * @param $imgname
4702
- * @return bool
4703
- */
4704
- function image_iscached($imgname)
4705
- {
4706
- return isset($this->imagelist[$imgname]);
4707
- }
4708
-
4709
- /**
4710
- * add a PNG image into the document, from a GD object
4711
- * this should work with remote files
4712
- *
4713
- * @param string $file The PNG file
4714
- * @param float $x X position
4715
- * @param float $y Y position
4716
- * @param float $w Width
4717
- * @param float $h Height
4718
- * @param resource $img A GD resource
4719
- * @param bool $is_mask true if the image is a mask
4720
- * @param bool $mask true if the image is masked
4721
- * @throws Exception
4722
- */
4723
- function addImagePng($file, $x, $y, $w = 0.0, $h = 0.0, &$img, $is_mask = false, $mask = null)
4724
- {
4725
- if (!function_exists("imagepng")) {
4726
- throw new \Exception("The PHP GD extension is required, but is not installed.");
4727
- }
4728
-
4729
- //if already cached, need not to read again
4730
- if (isset($this->imagelist[$file])) {
4731
- $data = null;
4732
- } else {
4733
- // Example for transparency handling on new image. Retain for current image
4734
- // $tIndex = imagecolortransparent($img);
4735
- // if ($tIndex > 0) {
4736
- // $tColor = imagecolorsforindex($img, $tIndex);
4737
- // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
4738
- // imagefill($new_img, 0, 0, $new_tIndex);
4739
- // imagecolortransparent($new_img, $new_tIndex);
4740
- // }
4741
- // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
4742
- //imagealphablending($img, true);
4743
-
4744
- //default, but explicitely set to ensure pdf compatibility
4745
- imagesavealpha($img, false/*!$is_mask && !$mask*/);
4746
-
4747
- $error = 0;
4748
- //DEBUG_IMG_TEMP
4749
- //debugpng
4750
- if (defined("DEBUGPNG") && DEBUGPNG) {
4751
- print '[addImagePng ' . $file . ']';
4752
- }
4753
-
4754
- ob_start();
4755
- @imagepng($img);
4756
- $data = ob_get_clean();
4757
-
4758
- if ($data == '') {
4759
- $error = 1;
4760
- $errormsg = 'trouble writing file from GD';
4761
- //DEBUG_IMG_TEMP
4762
- //debugpng
4763
- if (defined("DEBUGPNG") && DEBUGPNG) {
4764
- print 'trouble writing file from GD';
4765
- }
4766
- }
4767
-
4768
- if ($error) {
4769
- $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
4770
-
4771
- return;
4772
- }
4773
- } //End isset($this->imagelist[$file]) (png Duplicate removal)
4774
-
4775
- $this->addPngFromBuf($file, $x, $y, $w, $h, $data, $is_mask, $mask);
4776
- }
4777
-
4778
- /**
4779
- * @param $file
4780
- * @param $x
4781
- * @param $y
4782
- * @param $w
4783
- * @param $h
4784
- * @param $byte
4785
- */
4786
- protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
4787
- {
4788
- // generate images
4789
- $img = imagecreatefrompng($file);
4790
-
4791
- if ($img === false) {
4792
- return;
4793
- }
4794
-
4795
- // FIXME The pixel transformation doesn't work well with 8bit PNGs
4796
- $eight_bit = ($byte & 4) !== 4;
4797
-
4798
- $wpx = imagesx($img);
4799
- $hpx = imagesy($img);
4800
-
4801
- imagesavealpha($img, false);
4802
-
4803
- // create temp alpha file
4804
- $tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
4805
- @unlink($tempfile_alpha);
4806
- $tempfile_alpha = "$tempfile_alpha.png";
4807
-
4808
- // create temp plain file
4809
- $tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
4810
- @unlink($tempfile_plain);
4811
- $tempfile_plain = "$tempfile_plain.png";
4812
-
4813
- $imgalpha = imagecreate($wpx, $hpx);
4814
- imagesavealpha($imgalpha, false);
4815
-
4816
- // generate gray scale palette (0 -> 255)
4817
- for ($c = 0; $c < 256; ++$c) {
4818
- imagecolorallocate($imgalpha, $c, $c, $c);
4819
- }
4820
-
4821
- // Use PECL gmagick + Graphics Magic to process transparent PNG images
4822
- if (extension_loaded("gmagick")) {
4823
- $gmagick = new \Gmagick($file);
4824
- $gmagick->setimageformat('png');
4825
-
4826
- // Get opacity channel (negative of alpha channel)
4827
- $alpha_channel_neg = clone $gmagick;
4828
- $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
4829
-
4830
- // Negate opacity channel
4831
- $alpha_channel = new \Gmagick();
4832
- $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
4833
- $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
4834
- $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
4835
- $alpha_channel->writeimage($tempfile_alpha);
4836
-
4837
- // Cast to 8bit+palette
4838
- $imgalpha_ = imagecreatefrompng($tempfile_alpha);
4839
- imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
4840
- imagedestroy($imgalpha_);
4841
- imagepng($imgalpha, $tempfile_alpha);
4842
-
4843
- // Make opaque image
4844
- $color_channels = new \Gmagick();
4845
- $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
4846
- $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
4847
- $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
4848
- $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
4849
- $color_channels->writeimage($tempfile_plain);
4850
-
4851
- $imgplain = imagecreatefrompng($tempfile_plain);
4852
- }
4853
- // Use PECL imagick + ImageMagic to process transparent PNG images
4854
- elseif (extension_loaded("imagick")) {
4855
- // Native cloning was added to pecl-imagick in svn commit 263814
4856
- // the first version containing it was 3.0.1RC1
4857
- static $imagickClonable = null;
4858
- if ($imagickClonable === null) {
4859
- $imagickClonable = version_compare(Imagick::IMAGICK_EXTVER, '3.0.1rc1') > 0;
4860
- }
4861
-
4862
- $imagick = new \Imagick($file);
4863
- $imagick->setFormat('png');
4864
-
4865
- // Get opacity channel (negative of alpha channel)
4866
- if ($imagick->getImageAlphaChannel() !== 0) {
4867
- $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
4868
- $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
4869
- $alpha_channel->negateImage(true);
4870
- $alpha_channel->writeImage($tempfile_alpha);
4871
-
4872
- // Cast to 8bit+palette
4873
- $imgalpha_ = imagecreatefrompng($tempfile_alpha);
4874
- imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
4875
- imagedestroy($imgalpha_);
4876
- imagepng($imgalpha, $tempfile_alpha);
4877
- } else {
4878
- $tempfile_alpha = null;
4879
- }
4880
-
4881
- // Make opaque image
4882
- $color_channels = new \Imagick();
4883
- $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
4884
- $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
4885
- $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
4886
- $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
4887
- $color_channels->writeImage($tempfile_plain);
4888
-
4889
- $imgplain = imagecreatefrompng($tempfile_plain);
4890
- } else {
4891
- // allocated colors cache
4892
- $allocated_colors = array();
4893
-
4894
- // extract alpha channel
4895
- for ($xpx = 0; $xpx < $wpx; ++$xpx) {
4896
- for ($ypx = 0; $ypx < $hpx; ++$ypx) {
4897
- $color = imagecolorat($img, $xpx, $ypx);
4898
- $col = imagecolorsforindex($img, $color);
4899
- $alpha = $col['alpha'];
4900
-
4901
- if ($eight_bit) {
4902
- // with gamma correction
4903
- $gammacorr = 2.2;
4904
- $pixel = pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255;
4905
- } else {
4906
- // without gamma correction
4907
- $pixel = (127 - $alpha) * 2;
4908
-
4909
- $key = $col['red'] . $col['green'] . $col['blue'];
4910
-
4911
- if (!isset($allocated_colors[$key])) {
4912
- $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
4913
- $allocated_colors[$key] = $pixel_img;
4914
- } else {
4915
- $pixel_img = $allocated_colors[$key];
4916
- }
4917
-
4918
- imagesetpixel($img, $xpx, $ypx, $pixel_img);
4919
- }
4920
-
4921
- imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
4922
- }
4923
- }
4924
-
4925
- // extract image without alpha channel
4926
- $imgplain = imagecreatetruecolor($wpx, $hpx);
4927
- imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
4928
- imagedestroy($img);
4929
-
4930
- imagepng($imgalpha, $tempfile_alpha);
4931
- imagepng($imgplain, $tempfile_plain);
4932
- }
4933
-
4934
- // embed mask image
4935
- if ($tempfile_alpha) {
4936
- $this->addImagePng($tempfile_alpha, $x, $y, $w, $h, $imgalpha, true);
4937
- imagedestroy($imgalpha);
4938
- }
4939
-
4940
- // embed image, masked with previously embedded mask
4941
- $this->addImagePng($tempfile_plain, $x, $y, $w, $h, $imgplain, false, ($tempfile_alpha !== null));
4942
- imagedestroy($imgplain);
4943
-
4944
- // remove temp files
4945
- if ($tempfile_alpha) {
4946
- unlink($tempfile_alpha);
4947
- }
4948
- unlink($tempfile_plain);
4949
- }
4950
-
4951
- /**
4952
- * add a PNG image into the document, from a file
4953
- * this should work with remote files
4954
- *
4955
- * @param $file
4956
- * @param $x
4957
- * @param $y
4958
- * @param int $w
4959
- * @param int $h
4960
- * @throws Exception
4961
- */
4962
- function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
4963
- {
4964
- if (!function_exists("imagecreatefrompng")) {
4965
- throw new \Exception("The PHP GD extension is required, but is not installed.");
4966
- }
4967
-
4968
- //if already cached, need not to read again
4969
- if (isset($this->imagelist[$file])) {
4970
- $img = null;
4971
- } else {
4972
- $info = file_get_contents($file, false, null, 24, 5);
4973
- $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
4974
- $bit_depth = $meta["bitDepth"];
4975
- $color_type = $meta["colorType"];
4976
-
4977
- // http://www.w3.org/TR/PNG/#11IHDR
4978
- // 3 => indexed
4979
- // 4 => greyscale with alpha
4980
- // 6 => fullcolor with alpha
4981
- $is_alpha = in_array($color_type, array(4, 6)) || ($color_type == 3 && $bit_depth != 4);
4982
-
4983
- if ($is_alpha) { // exclude grayscale alpha
4984
- $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
4985
- return;
4986
- }
4987
-
4988
- //png files typically contain an alpha channel.
4989
- //pdf file format or class.pdf does not support alpha blending.
4990
- //on alpha blended images, more transparent areas have a color near black.
4991
- //This appears in the result on not storing the alpha channel.
4992
- //Correct would be the box background image or its parent when transparent.
4993
- //But this would make the image dependent on the background.
4994
- //Therefore create an image with white background and copy in
4995
- //A more natural background than black is white.
4996
- //Therefore create an empty image with white background and merge the
4997
- //image in with alpha blending.
4998
- $imgtmp = @imagecreatefrompng($file);
4999
- if (!$imgtmp) {
5000
- return;
5001
- }
5002
- $sx = imagesx($imgtmp);
5003
- $sy = imagesy($imgtmp);
5004
- $img = imagecreatetruecolor($sx, $sy);
5005
- imagealphablending($img, true);
5006
-
5007
- // @todo is it still needed ??
5008
- $ti = imagecolortransparent($imgtmp);
5009
- if ($ti >= 0) {
5010
- $tc = imagecolorsforindex($imgtmp, $ti);
5011
- $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5012
- imagefill($img, 0, 0, $ti);
5013
- imagecolortransparent($img, $ti);
5014
- } else {
5015
- imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5016
- }
5017
-
5018
- imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5019
- imagedestroy($imgtmp);
5020
- }
5021
- $this->addImagePng($file, $x, $y, $w, $h, $img);
5022
-
5023
- if ($img) {
5024
- imagedestroy($img);
5025
- }
5026
- }
5027
-
5028
- /**
5029
- * add a PNG image into the document, from a file
5030
- * this should work with remote files
5031
- *
5032
- * @param $file
5033
- * @param $x
5034
- * @param $y
5035
- * @param int $w
5036
- * @param int $h
5037
- */
5038
- function addSvgFromFile($file, $x, $y, $w = 0, $h = 0)
5039
- {
5040
- $doc = new \Svg\Document();
5041
- $doc->loadFile($file);
5042
- $dimensions = $doc->getDimensions();
5043
-
5044
- $this->save();
5045
-
5046
- $this->transform(array($w / $dimensions["width"], 0, 0, $h / $dimensions["height"], $x, $y));
5047
-
5048
- $surface = new \Svg\Surface\SurfaceCpdf($doc, $this);
5049
- $doc->render($surface);
5050
-
5051
- $this->restore();
5052
- }
5053
-
5054
- /**
5055
- * add a PNG image into the document, from a memory buffer of the file
5056
- *
5057
- * @param $file
5058
- * @param $x
5059
- * @param $y
5060
- * @param float $w
5061
- * @param float $h
5062
- * @param $data
5063
- * @param bool $is_mask
5064
- * @param null $mask
5065
- */
5066
- function addPngFromBuf($file, $x, $y, $w = 0.0, $h = 0.0, &$data, $is_mask = false, $mask = null)
5067
- {
5068
- if (isset($this->imagelist[$file])) {
5069
- $data = null;
5070
- $info['width'] = $this->imagelist[$file]['w'];
5071
- $info['height'] = $this->imagelist[$file]['h'];
5072
- $label = $this->imagelist[$file]['label'];
5073
- } else {
5074
- if ($data == null) {
5075
- $this->addMessage('addPngFromBuf error - data not present!');
5076
-
5077
- return;
5078
- }
5079
-
5080
- $error = 0;
5081
-
5082
- if (!$error) {
5083
- $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5084
-
5085
- if (mb_substr($data, 0, 8, '8bit') != $header) {
5086
- $error = 1;
5087
-
5088
- if (defined("DEBUGPNG") && DEBUGPNG) {
5089
- print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5090
- }
5091
-
5092
- $errormsg = 'this file does not have a valid header';
5093
- }
5094
- }
5095
-
5096
- if (!$error) {
5097
- // set pointer
5098
- $p = 8;
5099
- $len = mb_strlen($data, '8bit');
5100
-
5101
- // cycle through the file, identifying chunks
5102
- $haveHeader = 0;
5103
- $info = array();
5104
- $idata = '';
5105
- $pdata = '';
5106
-
5107
- while ($p < $len) {
5108
- $chunkLen = $this->getBytes($data, $p, 4);
5109
- $chunkType = mb_substr($data, $p + 4, 4, '8bit');
5110
-
5111
- switch ($chunkType) {
5112
- case 'IHDR':
5113
- // this is where all the file information comes from
5114
- $info['width'] = $this->getBytes($data, $p + 8, 4);
5115
- $info['height'] = $this->getBytes($data, $p + 12, 4);
5116
- $info['bitDepth'] = ord($data[$p + 16]);
5117
- $info['colorType'] = ord($data[$p + 17]);
5118
- $info['compressionMethod'] = ord($data[$p + 18]);
5119
- $info['filterMethod'] = ord($data[$p + 19]);
5120
- $info['interlaceMethod'] = ord($data[$p + 20]);
5121
-
5122
- //print_r($info);
5123
- $haveHeader = 1;
5124
- if ($info['compressionMethod'] != 0) {
5125
- $error = 1;
5126
-
5127
- //debugpng
5128
- if (defined("DEBUGPNG") && DEBUGPNG) {
5129
- print '[addPngFromFile unsupported compression method ' . $file . ']';
5130
- }
5131
-
5132
- $errormsg = 'unsupported compression method';
5133
- }
5134
-
5135
- if ($info['filterMethod'] != 0) {
5136
- $error = 1;
5137
-
5138
- //debugpng
5139
- if (defined("DEBUGPNG") && DEBUGPNG) {
5140
- print '[addPngFromFile unsupported filter method ' . $file . ']';
5141
- }
5142
-
5143
- $errormsg = 'unsupported filter method';
5144
- }
5145
- break;
5146
-
5147
- case 'PLTE':
5148
- $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5149
- break;
5150
-
5151
- case 'IDAT':
5152
- $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5153
- break;
5154
-
5155
- case 'tRNS':
5156
- //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
5157
- //print "tRNS found, color type = ".$info['colorType']."\n";
5158
- $transparency = array();
5159
-
5160
- switch ($info['colorType']) {
5161
- // indexed color, rbg
5162
- case 3:
5163
- /* corresponding to entries in the plte chunk
5164
- Alpha for palette index 0: 1 byte
5165
- Alpha for palette index 1: 1 byte
5166
- ...etc...
5167
- */
5168
- // there will be one entry for each palette entry. up until the last non-opaque entry.
5169
- // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
5170
- $transparency['type'] = 'indexed';
5171
- $trans = 0;
5172
-
5173
- for ($i = $chunkLen; $i >= 0; $i--) {
5174
- if (ord($data[$p + 8 + $i]) == 0) {
5175
- $trans = $i;
5176
- }
5177
- }
5178
-
5179
- $transparency['data'] = $trans;
5180
- break;
5181
-
5182
- // grayscale
5183
- case 0:
5184
- /* corresponding to entries in the plte chunk
5185
- Gray: 2 bytes, range 0 .. (2^bitdepth)-1
5186
- */
5187
- // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
5188
- $transparency['type'] = 'indexed';
5189
- $transparency['data'] = ord($data[$p + 8 + 1]);
5190
- break;
5191
-
5192
- // truecolor
5193
- case 2:
5194
- /* corresponding to entries in the plte chunk
5195
- Red: 2 bytes, range 0 .. (2^bitdepth)-1
5196
- Green: 2 bytes, range 0 .. (2^bitdepth)-1
5197
- Blue: 2 bytes, range 0 .. (2^bitdepth)-1
5198
- */
5199
- $transparency['r'] = $this->getBytes($data, $p + 8, 2);
5200
- // r from truecolor
5201
- $transparency['g'] = $this->getBytes($data, $p + 10, 2);
5202
- // g from truecolor
5203
- $transparency['b'] = $this->getBytes($data, $p + 12, 2);
5204
- // b from truecolor
5205
-
5206
- $transparency['type'] = 'color-key';
5207
- break;
5208
-
5209
- //unsupported transparency type
5210
- default:
5211
- if (defined("DEBUGPNG") && DEBUGPNG) {
5212
- print '[addPngFromFile unsupported transparency type ' . $file . ']';
5213
- }
5214
- break;
5215
- }
5216
-
5217
- // KS End new code
5218
- break;
5219
-
5220
- default:
5221
- break;
5222
- }
5223
-
5224
- $p += $chunkLen + 12;
5225
- }
5226
-
5227
- if (!$haveHeader) {
5228
- $error = 1;
5229
-
5230
- //debugpng
5231
- if (defined("DEBUGPNG") && DEBUGPNG) {
5232
- print '[addPngFromFile information header is missing ' . $file . ']';
5233
- }
5234
-
5235
- $errormsg = 'information header is missing';
5236
- }
5237
-
5238
- if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
5239
- $error = 1;
5240
-
5241
- //debugpng
5242
- if (defined("DEBUGPNG") && DEBUGPNG) {
5243
- print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
5244
- }
5245
-
5246
- $errormsg = 'There appears to be no support for interlaced images in pdf.';
5247
- }
5248
- }
5249
-
5250
- if (!$error && $info['bitDepth'] > 8) {
5251
- $error = 1;
5252
-
5253
- //debugpng
5254
- if (defined("DEBUGPNG") && DEBUGPNG) {
5255
- print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
5256
- }
5257
-
5258
- $errormsg = 'only bit depth of 8 or less is supported';
5259
- }
5260
-
5261
- if (!$error) {
5262
- switch ($info['colorType']) {
5263
- case 3:
5264
- $color = 'DeviceRGB';
5265
- $ncolor = 1;
5266
- break;
5267
-
5268
- case 2:
5269
- $color = 'DeviceRGB';
5270
- $ncolor = 3;
5271
- break;
5272
-
5273
- case 0:
5274
- $color = 'DeviceGray';
5275
- $ncolor = 1;
5276
- break;
5277
-
5278
- default:
5279
- $error = 1;
5280
-
5281
- //debugpng
5282
- if (defined("DEBUGPNG") && DEBUGPNG) {
5283
- print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
5284
- }
5285
-
5286
- $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
5287
- }
5288
- }
5289
-
5290
- if ($error) {
5291
- $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5292
-
5293
- return;
5294
- }
5295
-
5296
- //print_r($info);
5297
- // so this image is ok... add it in.
5298
- $this->numImages++;
5299
- $im = $this->numImages;
5300
- $label = "I$im";
5301
- $this->numObj++;
5302
-
5303
- // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
5304
- $options = array(
5305
- 'label' => $label,
5306
- 'data' => $idata,
5307
- 'bitsPerComponent' => $info['bitDepth'],
5308
- 'pdata' => $pdata,
5309
- 'iw' => $info['width'],
5310
- 'ih' => $info['height'],
5311
- 'type' => 'png',
5312
- 'color' => $color,
5313
- 'ncolor' => $ncolor,
5314
- 'masked' => $mask,
5315
- 'isMask' => $is_mask
5316
- );
5317
-
5318
- if (isset($transparency)) {
5319
- $options['transparency'] = $transparency;
5320
- }
5321
-
5322
- $this->o_image($this->numObj, 'new', $options);
5323
- $this->imagelist[$file] = array('label' => $label, 'w' => $info['width'], 'h' => $info['height']);
5324
- }
5325
-
5326
- if ($is_mask) {
5327
- return;
5328
- }
5329
-
5330
- if ($w <= 0 && $h <= 0) {
5331
- $w = $info['width'];
5332
- $h = $info['height'];
5333
- }
5334
-
5335
- if ($w <= 0) {
5336
- $w = $h / $info['height'] * $info['width'];
5337
- }
5338
-
5339
- if ($h <= 0) {
5340
- $h = $w * $info['height'] / $info['width'];
5341
- }
5342
-
5343
- $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
5344
- }
5345
-
5346
- /**
5347
- * add a JPEG image into the document, from a file
5348
- *
5349
- * @param $img
5350
- * @param $x
5351
- * @param $y
5352
- * @param int $w
5353
- * @param int $h
5354
- */
5355
- function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
5356
- {
5357
- // attempt to add a jpeg image straight from a file, using no GD commands
5358
- // note that this function is unable to operate on a remote file.
5359
-
5360
- if (!file_exists($img)) {
5361
- return;
5362
- }
5363
-
5364
- if ($this->image_iscached($img)) {
5365
- $data = null;
5366
- $imageWidth = $this->imagelist[$img]['w'];
5367
- $imageHeight = $this->imagelist[$img]['h'];
5368
- $channels = $this->imagelist[$img]['c'];
5369
- } else {
5370
- $tmp = getimagesize($img);
5371
- $imageWidth = $tmp[0];
5372
- $imageHeight = $tmp[1];
5373
-
5374
- if (isset($tmp['channels'])) {
5375
- $channels = $tmp['channels'];
5376
- } else {
5377
- $channels = 3;
5378
- }
5379
-
5380
- $data = file_get_contents($img);
5381
- }
5382
-
5383
- if ($w <= 0 && $h <= 0) {
5384
- $w = $imageWidth;
5385
- }
5386
-
5387
- if ($w == 0) {
5388
- $w = $h / $imageHeight * $imageWidth;
5389
- }
5390
-
5391
- if ($h == 0) {
5392
- $h = $w * $imageHeight / $imageWidth;
5393
- }
5394
-
5395
- $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels, $img);
5396
- }
5397
-
5398
- /**
5399
- * common code used by the two JPEG adding functions
5400
- * @param $data
5401
- * @param $x
5402
- * @param $y
5403
- * @param int $w
5404
- * @param int $h
5405
- * @param $imageWidth
5406
- * @param $imageHeight
5407
- * @param int $channels
5408
- * @param $imgname
5409
- */
5410
- private function addJpegImage_common(
5411
- &$data,
5412
- $x,
5413
- $y,
5414
- $w = 0,
5415
- $h = 0,
5416
- $imageWidth,
5417
- $imageHeight,
5418
- $channels = 3,
5419
- $imgname
5420
- ) {
5421
- if ($this->image_iscached($imgname)) {
5422
- $label = $this->imagelist[$imgname]['label'];
5423
- //debugpng
5424
- //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
5425
-
5426
- } else {
5427
- if ($data == null) {
5428
- $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
5429
-
5430
- return;
5431
- }
5432
-
5433
- // note that this function is not to be called externally
5434
- // it is just the common code between the GD and the file options
5435
- $this->numImages++;
5436
- $im = $this->numImages;
5437
- $label = "I$im";
5438
- $this->numObj++;
5439
-
5440
- $this->o_image(
5441
- $this->numObj,
5442
- 'new',
5443
- array(
5444
- 'label' => $label,
5445
- 'data' => &$data,
5446
- 'iw' => $imageWidth,
5447
- 'ih' => $imageHeight,
5448
- 'channels' => $channels
5449
- )
5450
- );
5451
-
5452
- $this->imagelist[$imgname] = array(
5453
- 'label' => $label,
5454
- 'w' => $imageWidth,
5455
- 'h' => $imageHeight,
5456
- 'c' => $channels
5457
- );
5458
- }
5459
-
5460
- $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
5461
- }
5462
-
5463
- /**
5464
- * specify where the document should open when it first starts
5465
- *
5466
- * @param $style
5467
- * @param int $a
5468
- * @param int $b
5469
- * @param int $c
5470
- */
5471
- function openHere($style, $a = 0, $b = 0, $c = 0)
5472
- {
5473
- // this function will open the document at a specified page, in a specified style
5474
- // the values for style, and the required parameters are:
5475
- // 'XYZ' left, top, zoom
5476
- // 'Fit'
5477
- // 'FitH' top
5478
- // 'FitV' left
5479
- // 'FitR' left,bottom,right
5480
- // 'FitB'
5481
- // 'FitBH' top
5482
- // 'FitBV' left
5483
- $this->numObj++;
5484
- $this->o_destination(
5485
- $this->numObj,
5486
- 'new',
5487
- array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)
5488
- );
5489
- $id = $this->catalogId;
5490
- $this->o_catalog($id, 'openHere', $this->numObj);
5491
- }
5492
-
5493
- /**
5494
- * Add JavaScript code to the PDF document
5495
- *
5496
- * @param string $code
5497
- */
5498
- function addJavascript($code)
5499
- {
5500
- $this->javascript .= $code;
5501
- }
5502
-
5503
- /**
5504
- * create a labelled destination within the document
5505
- *
5506
- * @param $label
5507
- * @param $style
5508
- * @param int $a
5509
- * @param int $b
5510
- * @param int $c
5511
- */
5512
- function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
5513
- {
5514
- // associates the given label with the destination, it is done this way so that a destination can be specified after
5515
- // it has been linked to
5516
- // styles are the same as the 'openHere' function
5517
- $this->numObj++;
5518
- $this->o_destination(
5519
- $this->numObj,
5520
- 'new',
5521
- array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)
5522
- );
5523
- $id = $this->numObj;
5524
-
5525
- // store the label->idf relationship, note that this means that labels can be used only once
5526
- $this->destinations["$label"] = $id;
5527
- }
5528
-
5529
- /**
5530
- * define font families, this is used to initialize the font families for the default fonts
5531
- * and for the user to add new ones for their fonts. The default bahavious can be overridden should
5532
- * that be desired.
5533
- *
5534
- * @param $family
5535
- * @param string $options
5536
- */
5537
- function setFontFamily($family, $options = '')
5538
- {
5539
- if (!is_array($options)) {
5540
- if ($family === 'init') {
5541
- // set the known family groups
5542
- // these font families will be used to enable bold and italic markers to be included
5543
- // within text streams. html forms will be used... <b></b> <i></i>
5544
- $this->fontFamilies['Helvetica.afm'] =
5545
- array(
5546
- 'b' => 'Helvetica-Bold.afm',
5547
- 'i' => 'Helvetica-Oblique.afm',
5548
- 'bi' => 'Helvetica-BoldOblique.afm',
5549
- 'ib' => 'Helvetica-BoldOblique.afm'
5550
- );
5551
-
5552
- $this->fontFamilies['Courier.afm'] =
5553
- array(
5554
- 'b' => 'Courier-Bold.afm',
5555
- 'i' => 'Courier-Oblique.afm',
5556
- 'bi' => 'Courier-BoldOblique.afm',
5557
- 'ib' => 'Courier-BoldOblique.afm'
5558
- );
5559
-
5560
- $this->fontFamilies['Times-Roman.afm'] =
5561
- array(
5562
- 'b' => 'Times-Bold.afm',
5563
- 'i' => 'Times-Italic.afm',
5564
- 'bi' => 'Times-BoldItalic.afm',
5565
- 'ib' => 'Times-BoldItalic.afm'
5566
- );
5567
- }
5568
- } else {
5569
-
5570
- // the user is trying to set a font family
5571
- // note that this can also be used to set the base ones to something else
5572
- if (mb_strlen($family)) {
5573
- $this->fontFamilies[$family] = $options;
5574
- }
5575
- }
5576
- }
5577
-
5578
- /**
5579
- * used to add messages for use in debugging
5580
- *
5581
- * @param $message
5582
- */
5583
- function addMessage($message)
5584
- {
5585
- $this->messages .= $message . "\n";
5586
- }
5587
-
5588
- /**
5589
- * a few functions which should allow the document to be treated transactionally.
5590
- *
5591
- * @param $action
5592
- */
5593
- function transaction($action)
5594
- {
5595
- switch ($action) {
5596
- case 'start':
5597
- // store all the data away into the checkpoint variable
5598
- $data = get_object_vars($this);
5599
- $this->checkpoint = $data;
5600
- unset($data);
5601
- break;
5602
-
5603
- case 'commit':
5604
- if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
5605
- $tmp = $this->checkpoint['checkpoint'];
5606
- $this->checkpoint = $tmp;
5607
- unset($tmp);
5608
- } else {
5609
- $this->checkpoint = '';
5610
- }
5611
- break;
5612
-
5613
- case 'rewind':
5614
- // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
5615
- if (is_array($this->checkpoint)) {
5616
- // can only abort if were inside a checkpoint
5617
- $tmp = $this->checkpoint;
5618
-
5619
- foreach ($tmp as $k => $v) {
5620
- if ($k !== 'checkpoint') {
5621
- $this->$k = $v;
5622
- }
5623
- }
5624
- unset($tmp);
5625
- }
5626
- break;
5627
-
5628
- case 'abort':
5629
- if (is_array($this->checkpoint)) {
5630
- // can only abort if were inside a checkpoint
5631
- $tmp = $this->checkpoint;
5632
- foreach ($tmp as $k => $v) {
5633
- $this->$k = $v;
5634
- }
5635
- unset($tmp);
5636
- }
5637
- break;
5638
- }
5639
- }
5640
- }
 
 
 
 
1
+ <?php
2
+ /**
3
+ * A PHP class to provide the basic functionality to create a pdf document without
4
+ * any requirement for additional modules.
5
+ *
6
+ * Extended by Orion Richardson to support Unicode / UTF-8 characters using
7
+ * TCPDF and others as a guide.
8
+ *
9
+ * @author Wayne Munro <pdf@ros.co.nz>
10
+ * @author Orion Richardson <orionr@yahoo.com>
11
+ * @author Helmut Tischer <htischer@weihenstephan.org>
12
+ * @author Ryan H. Masten <ryan.masten@gmail.com>
13
+ * @author Brian Sweeney <eclecticgeek@gmail.com>
14
+ * @author Fabien Ménager <fabien.menager@gmail.com>
15
+ * @license Public Domain http://creativecommons.org/licenses/publicdomain/
16
+ * @package Cpdf
17
+ */
18
+ use FontLib\Font;
19
+ use FontLib\BinaryStream;
20
+
21
+ class Cpdf
22
+ {
23
+
24
+ /**
25
+ * @var integer The current number of pdf objects in the document
26
+ */
27
+ public $numObj = 0;
28
+
29
+ /**
30
+ * @var array This array contains all of the pdf objects, ready for final assembly
31
+ */
32
+ public $objects = array();
33
+
34
+ /**
35
+ * @var integer The objectId (number within the objects array) of the document catalog
36
+ */
37
+ public $catalogId;
38
+
39
+ /**
40
+ * @var array Array carrying information about the fonts that the system currently knows about
41
+ * Used to ensure that a font is not loaded twice, among other things
42
+ */
43
+ public $fonts = array();
44
+
45
+ /**
46
+ * @var string The default font metrics file to use if no other font has been loaded.
47
+ * The path to the directory containing the font metrics should be included
48
+ */
49
+ public $defaultFont = './fonts/Helvetica.afm';
50
+
51
+ /**
52
+ * @string A record of the current font
53
+ */
54
+ public $currentFont = '';
55
+
56
+ /**
57
+ * @var string The current base font
58
+ */
59
+ public $currentBaseFont = '';
60
+
61
+ /**
62
+ * @var integer The number of the current font within the font array
63
+ */
64
+ public $currentFontNum = 0;
65
+
66
+ /**
67
+ * @var integer
68
+ */
69
+ public $currentNode;
70
+
71
+ /**
72
+ * @var integer Object number of the current page
73
+ */
74
+ public $currentPage;
75
+
76
+ /**
77
+ * @var integer Object number of the currently active contents block
78
+ */
79
+ public $currentContents;
80
+
81
+ /**
82
+ * @var integer Number of fonts within the system
83
+ */
84
+ public $numFonts = 0;
85
+
86
+ /**
87
+ * @var integer Number of graphic state resources used
88
+ */
89
+ private $numStates = 0;
90
+
91
+ /**
92
+ * @var array Number of graphic state resources used
93
+ */
94
+ private $gstates = array();
95
+
96
+ /**
97
+ * @var array Current color for fill operations, defaults to inactive value,
98
+ * all three components should be between 0 and 1 inclusive when active
99
+ */
100
+ public $currentColor = null;
101
+
102
+ /**
103
+ * @var array Current color for stroke operations (lines etc.)
104
+ */
105
+ public $currentStrokeColor = null;
106
+
107
+ /**
108
+ * @var string Fill rule (nonzero or evenodd)
109
+ */
110
+ public $fillRule = "nonzero";
111
+
112
+ /**
113
+ * @var string Current style that lines are drawn in
114
+ */
115
+ public $currentLineStyle = '';
116
+
117
+ /**
118
+ * @var array Current line transparency (partial graphics state)
119
+ */
120
+ public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
121
+
122
+ /**
123
+ * array Current fill transparency (partial graphics state)
124
+ */
125
+ public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
126
+
127
+ /**
128
+ * @var array An array which is used to save the state of the document, mainly the colors and styles
129
+ * it is used to temporarily change to another state, then change back to what it was before
130
+ */
131
+ public $stateStack = array();
132
+
133
+ /**
134
+ * @var integer Number of elements within the state stack
135
+ */
136
+ public $nStateStack = 0;
137
+
138
+ /**
139
+ * @var integer Number of page objects within the document
140
+ */
141
+ public $numPages = 0;
142
+
143
+ /**
144
+ * @var array Object Id storage stack
145
+ */
146
+ public $stack = array();
147
+
148
+ /**
149
+ * @var integer Number of elements within the object Id storage stack
150
+ */
151
+ public $nStack = 0;
152
+
153
+ /**
154
+ * an array which contains information about the objects which are not firmly attached to pages
155
+ * these have been added with the addObject function
156
+ */
157
+ public $looseObjects = array();
158
+
159
+ /**
160
+ * array contains information about how the loose objects are to be added to the document
161
+ */
162
+ public $addLooseObjects = array();
163
+
164
+ /**
165
+ * @var integer The objectId of the information object for the document
166
+ * this contains authorship, title etc.
167
+ */
168
+ public $infoObject = 0;
169
+
170
+ /**
171
+ * @var integer Number of images being tracked within the document
172
+ */
173
+ public $numImages = 0;
174
+
175
+ /**
176
+ * @var array An array containing options about the document
177
+ * it defaults to turning on the compression of the objects
178
+ */
179
+ public $options = array('compression' => true);
180
+
181
+ /**
182
+ * @var integer The objectId of the first page of the document
183
+ */
184
+ public $firstPageId;
185
+
186
+ /**
187
+ * @var integer The object Id of the procset object
188
+ */
189
+ public $procsetObjectId;
190
+
191
+ /**
192
+ * @var array Store the information about the relationship between font families
193
+ * this used so that the code knows which font is the bold version of another font, etc.
194
+ * the value of this array is initialised in the constructor function.
195
+ */
196
+ public $fontFamilies = array();
197
+
198
+ /**
199
+ * @var string Folder for php serialized formats of font metrics files.
200
+ * If empty string, use same folder as original metrics files.
201
+ * This can be passed in from class creator.
202
+ * If this folder does not exist or is not writable, Cpdf will be **much** slower.
203
+ * Because of potential trouble with php safe mode, folder cannot be created at runtime.
204
+ */
205
+ public $fontcache = '';
206
+
207
+ /**
208
+ * @var integer The version of the font metrics cache file.
209
+ * This value must be manually incremented whenever the internal font data structure is modified.
210
+ */
211
+ public $fontcacheVersion = 6;
212
+
213
+ /**
214
+ * @var string Temporary folder.
215
+ * If empty string, will attempt system tmp folder.
216
+ * This can be passed in from class creator.
217
+ */
218
+ public $tmp = '';
219
+
220
+ /**
221
+ * @var string Track if the current font is bolded or italicised
222
+ */
223
+ public $currentTextState = '';
224
+
225
+ /**
226
+ * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
227
+ */
228
+ public $messages = '';
229
+
230
+ /**
231
+ * @var string The encryption array for the document encryption is stored here
232
+ */
233
+ public $arc4 = '';
234
+
235
+ /**
236
+ * @var integer The object Id of the encryption information
237
+ */
238
+ public $arc4_objnum = 0;
239
+
240
+ /**
241
+ * @var string The file identifier, used to uniquely identify a pdf document
242
+ */
243
+ public $fileIdentifier = '';
244
+
245
+ /**
246
+ * @var boolean A flag to say if a document is to be encrypted or not
247
+ */
248
+ public $encrypted = false;
249
+
250
+ /**
251
+ * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
252
+ */
253
+ public $encryptionKey = '';
254
+
255
+ /**
256
+ * @var array Array which forms a stack to keep track of nested callback functions
257
+ */
258
+ public $callback = array();
259
+
260
+ /**
261
+ * @var integer The number of callback functions in the callback array
262
+ */
263
+ public $nCallback = 0;
264
+
265
+ /**
266
+ * @var array Store label->id pairs for named destinations, these will be used to replace internal links
267
+ * done this way so that destinations can be defined after the location that links to them
268
+ */
269
+ public $destinations = array();
270
+
271
+ /**
272
+ * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
273
+ * publiciables within the class, so that the user can rollback at will (from each 'start' command)
274
+ * note that this includes the objects array, so these can be large.
275
+ */
276
+ public $checkpoint = '';
277
+
278
+ /**
279
+ * @var array Table of Image origin filenames and image labels which were already added with o_image().
280
+ * Allows to merge identical images
281
+ */
282
+ public $imagelist = array();
283
+
284
+ /**
285
+ * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
286
+ */
287
+ public $isUnicode = false;
288
+
289
+ /**
290
+ * @var string the JavaScript code of the document
291
+ */
292
+ public $javascript = '';
293
+
294
+ /**
295
+ * @var boolean whether the compression is possible
296
+ */
297
+ protected $compressionReady = false;
298
+
299
+ /**
300
+ * @var array Current page size
301
+ */
302
+ protected $currentPageSize = array("width" => 0, "height" => 0);
303
+
304
+ /**
305
+ * @var array All the chars that will be required in the font subsets
306
+ */
307
+ protected $stringSubsets = array();
308
+
309
+ /**
310
+ * @var string The target internal encoding
311
+ */
312
+ static protected $targetEncoding = 'Windows-1252';
313
+
314
+ /**
315
+ * @var array The list of the core fonts
316
+ */
317
+ static protected $coreFonts = array(
318
+ 'courier',
319
+ 'courier-bold',
320
+ 'courier-oblique',
321
+ 'courier-boldoblique',
322
+ 'helvetica',
323
+ 'helvetica-bold',
324
+ 'helvetica-oblique',
325
+ 'helvetica-boldoblique',
326
+ 'times-roman',
327
+ 'times-bold',
328
+ 'times-italic',
329
+ 'times-bolditalic',
330
+ 'symbol',
331
+ 'zapfdingbats'
332
+ );
333
+
334
+ /**
335
+ * Class constructor
336
+ * This will start a new document
337
+ *
338
+ * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
339
+ * @param boolean $isUnicode Whether text will be treated as Unicode or not.
340
+ * @param string $fontcache The font cache folder
341
+ * @param string $tmp The temporary folder
342
+ */
343
+ function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '')
344
+ {
345
+ $this->isUnicode = $isUnicode;
346
+ $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
347
+ $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
348
+ $this->newDocument($pageSize);
349
+
350
+ $this->compressionReady = function_exists('gzcompress');
351
+
352
+ if (in_array('Windows-1252', mb_list_encodings())) {
353
+ self::$targetEncoding = 'Windows-1252';
354
+ }
355
+
356
+ // also initialize the font families that are known about already
357
+ $this->setFontFamily('init');
358
+ }
359
+
360
+ /**
361
+ * Document object methods (internal use only)
362
+ *
363
+ * There is about one object method for each type of object in the pdf document
364
+ * Each function has the same call list ($id,$action,$options).
365
+ * $id = the object ID of the object, or what it is to be if it is being created
366
+ * $action = a string specifying the action to be performed, though ALL must support:
367
+ * 'new' - create the object with the id $id
368
+ * 'out' - produce the output for the pdf object
369
+ * $options = optional, a string or array containing the various parameters for the object
370
+ *
371
+ * These, in conjunction with the output function are the ONLY way for output to be produced
372
+ * within the pdf 'file'.
373
+ */
374
+
375
+ /**
376
+ * Destination object, used to specify the location for the user to jump to, presently on opening
377
+ *
378
+ * @param $id
379
+ * @param $action
380
+ * @param string $options
381
+ * @return string|null
382
+ */
383
+ protected function o_destination($id, $action, $options = '')
384
+ {
385
+ switch ($action) {
386
+ case 'new':
387
+ $this->objects[$id] = array('t' => 'destination', 'info' => array());
388
+ $tmp = '';
389
+ switch ($options['type']) {
390
+ case 'XYZ':
391
+ /** @noinspection PhpMissingBreakStatementInspection */
392
+ case 'FitR':
393
+ $tmp = ' ' . $options['p3'] . $tmp;
394
+ case 'FitH':
395
+ case 'FitV':
396
+ case 'FitBH':
397
+ /** @noinspection PhpMissingBreakStatementInspection */
398
+ case 'FitBV':
399
+ $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
400
+ case 'Fit':
401
+ case 'FitB':
402
+ $tmp = $options['type'] . $tmp;
403
+ $this->objects[$id]['info']['string'] = $tmp;
404
+ $this->objects[$id]['info']['page'] = $options['page'];
405
+ }
406
+ break;
407
+
408
+ case 'out':
409
+ $o = &$this->objects[$id];
410
+
411
+ $tmp = $o['info'];
412
+ $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
413
+
414
+ return $res;
415
+ }
416
+
417
+ return null;
418
+ }
419
+
420
+ /**
421
+ * set the viewer preferences
422
+ *
423
+ * @param $id
424
+ * @param $action
425
+ * @param string|array $options
426
+ * @return string|null
427
+ */
428
+ protected function o_viewerPreferences($id, $action, $options = '')
429
+ {
430
+ switch ($action) {
431
+ case 'new':
432
+ $this->objects[$id] = array('t' => 'viewerPreferences', 'info' => array());
433
+ break;
434
+
435
+ case 'add':
436
+ $o = &$this->objects[$id];
437
+
438
+ foreach ($options as $k => $v) {
439
+ switch ($k) {
440
+ // Boolean keys
441
+ case 'HideToolbar':
442
+ case 'HideMenubar':
443
+ case 'HideWindowUI':
444
+ case 'FitWindow':
445
+ case 'CenterWindow':
446
+ case 'DisplayDocTitle':
447
+ case 'PickTrayByPDFSize':
448
+ $o['info'][$k] = (bool)$v;
449
+ break;
450
+
451
+ // Integer keys
452
+ case 'NumCopies':
453
+ $o['info'][$k] = (int)$v;
454
+ break;
455
+
456
+ // Name keys
457
+ case 'ViewArea':
458
+ case 'ViewClip':
459
+ case 'PrintClip':
460
+ case 'PrintArea':
461
+ $o['info'][$k] = (string)$v;
462
+ break;
463
+
464
+ // Named with limited valid values
465
+ case 'NonFullScreenPageMode':
466
+ if (!in_array($v, array('UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'))) {
467
+ break;
468
+ }
469
+ $o['info'][$k] = $v;
470
+ break;
471
+
472
+ case 'Direction':
473
+ if (!in_array($v, array('L2R', 'R2L'))) {
474
+ break;
475
+ }
476
+ $o['info'][$k] = $v;
477
+ break;
478
+
479
+ case 'PrintScaling':
480
+ if (!in_array($v, array('None', 'AppDefault'))) {
481
+ break;
482
+ }
483
+ $o['info'][$k] = $v;
484
+ break;
485
+
486
+ case 'Duplex':
487
+ if (!in_array($v, array('None', 'AppDefault'))) {
488
+ break;
489
+ }
490
+ $o['info'][$k] = $v;
491
+ break;
492
+
493
+ // Integer array
494
+ case 'PrintPageRange':
495
+ // Cast to integer array
496
+ foreach ($v as $vK => $vV) {
497
+ $v[$vK] = (int)$vV;
498
+ }
499
+ $o['info'][$k] = array_values($v);
500
+ break;
501
+ }
502
+ }
503
+ break;
504
+
505
+ case 'out':
506
+ $o = &$this->objects[$id];
507
+ $res = "\n$id 0 obj\n<< ";
508
+
509
+ foreach ($o['info'] as $k => $v) {
510
+ if (is_string($v)) {
511
+ $v = '/' . $v;
512
+ } elseif (is_int($v)) {
513
+ $v = (string) $v;
514
+ } elseif (is_bool($v)) {
515
+ $v = ($v ? 'true' : 'false');
516
+ } elseif (is_array($v)) {
517
+ $v = '[' . implode(' ', $v) . ']';
518
+ }
519
+ $res .= "\n/$k $v";
520
+ }
521
+ $res .= "\n>>\n";
522
+
523
+ return $res;
524
+ }
525
+
526
+ return null;
527
+ }
528
+
529
+ /**
530
+ * define the document catalog, the overall controller for the document
531
+ *
532
+ * @param $id
533
+ * @param $action
534
+ * @param string|array $options
535
+ * @return string|null
536
+ */
537
+ protected function o_catalog($id, $action, $options = '')
538
+ {
539
+ if ($action !== 'new') {
540
+ $o = &$this->objects[$id];
541
+ }
542
+
543
+ switch ($action) {
544
+ case 'new':
545
+ $this->objects[$id] = array('t' => 'catalog', 'info' => array());
546
+ $this->catalogId = $id;
547
+ break;
548
+
549
+ case 'outlines':
550
+ case 'pages':
551
+ case 'openHere':
552
+ case 'javascript':
553
+ $o['info'][$action] = $options;
554
+ break;
555
+
556
+ case 'viewerPreferences':
557
+ if (!isset($o['info']['viewerPreferences'])) {
558
+ $this->numObj++;
559
+ $this->o_viewerPreferences($this->numObj, 'new');
560
+ $o['info']['viewerPreferences'] = $this->numObj;
561
+ }
562
+
563
+ $vp = $o['info']['viewerPreferences'];
564
+ $this->o_viewerPreferences($vp, 'add', $options);
565
+
566
+ break;
567
+
568
+ case 'out':
569
+ $res = "\n$id 0 obj\n<< /Type /Catalog";
570
+
571
+ foreach ($o['info'] as $k => $v) {
572
+ switch ($k) {
573
+ case 'outlines':
574
+ $res .= "\n/Outlines $v 0 R";
575
+ break;
576
+
577
+ case 'pages':
578
+ $res .= "\n/Pages $v 0 R";
579
+ break;
580
+
581
+ case 'viewerPreferences':
582
+ $res .= "\n/ViewerPreferences $v 0 R";
583
+ break;
584
+
585
+ case 'openHere':
586
+ $res .= "\n/OpenAction $v 0 R";
587
+ break;
588
+
589
+ case 'javascript':
590
+ $res .= "\n/Names <</JavaScript $v 0 R>>";
591
+ break;
592
+ }
593
+ }
594
+
595
+ $res .= " >>\nendobj";
596
+
597
+ return $res;
598
+ }
599
+
600
+ return null;
601
+ }
602
+
603
+ /**
604
+ * object which is a parent to the pages in the document
605
+ *
606
+ * @param $id
607
+ * @param $action
608
+ * @param string $options
609
+ * @return string|null
610
+ */
611
+ protected function o_pages($id, $action, $options = '')
612
+ {
613
+ if ($action !== 'new') {
614
+ $o = &$this->objects[$id];
615
+ }
616
+
617
+ switch ($action) {
618
+ case 'new':
619
+ $this->objects[$id] = array('t' => 'pages', 'info' => array());
620
+ $this->o_catalog($this->catalogId, 'pages', $id);
621
+ break;
622
+
623
+ case 'page':
624
+ if (!is_array($options)) {
625
+ // then it will just be the id of the new page
626
+ $o['info']['pages'][] = $options;
627
+ } else {
628
+ // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
629
+ // and pos is either 'before' or 'after', saying where this page will fit.
630
+ if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
631
+ $i = array_search($options['rid'], $o['info']['pages']);
632
+ if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
633
+
634
+ // then there is a match
635
+ // make a space
636
+ switch ($options['pos']) {
637
+ case 'before':
638
+ $k = $i;
639
+ break;
640
+
641
+ case 'after':
642
+ $k = $i + 1;
643
+ break;
644
+
645
+ default:
646
+ $k = -1;
647
+ break;
648
+ }
649
+
650
+ if ($k >= 0) {
651
+ for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
652
+ $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
653
+ }
654
+
655
+ $o['info']['pages'][$k] = $options['id'];
656
+ }
657
+ }
658
+ }
659
+ }
660
+ break;
661
+
662
+ case 'procset':
663
+ $o['info']['procset'] = $options;
664
+ break;
665
+
666
+ case 'mediaBox':
667
+ $o['info']['mediaBox'] = $options;
668
+ // which should be an array of 4 numbers
669
+ $this->currentPageSize = array('width' => $options[2], 'height' => $options[3]);
670
+ break;
671
+
672
+ case 'font':
673
+ $o['info']['fonts'][] = array('objNum' => $options['objNum'], 'fontNum' => $options['fontNum']);
674
+ break;
675
+
676
+ case 'extGState':
677
+ $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']);
678
+ break;
679
+
680
+ case 'xObject':
681
+ $o['info']['xObjects'][] = array('objNum' => $options['objNum'], 'label' => $options['label']);
682
+ break;
683
+
684
+ case 'out':
685
+ if (count($o['info']['pages'])) {
686
+ $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
687
+ foreach ($o['info']['pages'] as $v) {
688
+ $res .= "$v 0 R\n";
689
+ }
690
+
691
+ $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
692
+
693
+ if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
694
+ isset($o['info']['procset']) ||
695
+ (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
696
+ ) {
697
+ $res .= "\n/Resources <<";
698
+
699
+ if (isset($o['info']['procset'])) {
700
+ $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
701
+ }
702
+
703
+ if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
704
+ $res .= "\n/Font << ";
705
+ foreach ($o['info']['fonts'] as $finfo) {
706
+ $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
707
+ }
708
+ $res .= "\n>>";
709
+ }
710
+
711
+ if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
712
+ $res .= "\n/XObject << ";
713
+ foreach ($o['info']['xObjects'] as $finfo) {
714
+ $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
715
+ }
716
+ $res .= "\n>>";
717
+ }
718
+
719
+ if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
720
+ $res .= "\n/ExtGState << ";
721
+ foreach ($o['info']['extGStates'] as $gstate) {
722
+ $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
723
+ }
724
+ $res .= "\n>>";
725
+ }
726
+
727
+ $res .= "\n>>";
728
+ if (isset($o['info']['mediaBox'])) {
729
+ $tmp = $o['info']['mediaBox'];
730
+ $res .= "\n/MediaBox [" . sprintf(
731
+ '%.3F %.3F %.3F %.3F',
732
+ $tmp[0],
733
+ $tmp[1],
734
+ $tmp[2],
735
+ $tmp[3]
736
+ ) . ']';
737
+ }
738
+ }
739
+
740
+ $res .= "\n >>\nendobj";
741
+ } else {
742
+ $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
743
+ }
744
+
745
+ return $res;
746
+ }
747
+
748
+ return null;
749
+ }
750
+
751
+ /**
752
+ * define the outlines in the doc, empty for now
753
+ *
754
+ * @param $id
755
+ * @param $action
756
+ * @param string $options
757
+ * @return string|null
758
+ */
759
+ protected function o_outlines($id, $action, $options = '')
760
+ {
761
+ if ($action !== 'new') {
762
+ $o = &$this->objects[$id];
763
+ }
764
+
765
+ switch ($action) {
766
+ case 'new':
767
+ $this->objects[$id] = array('t' => 'outlines', 'info' => array('outlines' => array()));
768
+ $this->o_catalog($this->catalogId, 'outlines', $id);
769
+ break;
770
+
771
+ case 'outline':
772
+ $o['info']['outlines'][] = $options;
773
+ break;
774
+
775
+ case 'out':
776
+ if (count($o['info']['outlines'])) {
777
+ $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
778
+ foreach ($o['info']['outlines'] as $v) {
779
+ $res .= "$v 0 R ";
780
+ }
781
+
782
+ $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
783
+ } else {
784
+ $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
785
+ }
786
+
787
+ return $res;
788
+ }
789
+
790
+ return null;
791
+ }
792
+
793
+ /**
794
+ * an object to hold the font description
795
+ *
796
+ * @param $id
797
+ * @param $action
798
+ * @param string|array $options
799
+ * @return string|null
800
+ */
801
+ protected function o_font($id, $action, $options = '')
802
+ {
803
+ if ($action !== 'new') {
804
+ $o = &$this->objects[$id];
805
+ }
806
+
807
+ switch ($action) {
808
+ case 'new':
809
+ $this->objects[$id] = array(
810
+ 't' => 'font',
811
+ 'info' => array(
812
+ 'name' => $options['name'],
813
+ 'fontFileName' => $options['fontFileName'],
814
+ 'SubType' => 'Type1'
815
+ )
816
+ );
817
+ $fontNum = $this->numFonts;
818
+ $this->objects[$id]['info']['fontNum'] = $fontNum;
819
+
820
+ // deal with the encoding and the differences
821
+ if (isset($options['differences'])) {
822
+ // then we'll need an encoding dictionary
823
+ $this->numObj++;
824
+ $this->o_fontEncoding($this->numObj, 'new', $options);
825
+ $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
826
+ } else {
827
+ if (isset($options['encoding'])) {
828
+ // we can specify encoding here
829
+ switch ($options['encoding']) {
830
+ case 'WinAnsiEncoding':
831
+ case 'MacRomanEncoding':
832
+ case 'MacExpertEncoding':
833
+ $this->objects[$id]['info']['encoding'] = $options['encoding'];
834
+ break;
835
+
836
+ case 'none':
837
+ break;
838
+
839
+ default:
840
+ $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
841
+ break;
842
+ }
843
+ } else {
844
+ $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
845
+ }
846
+ }
847
+
848
+ if ($this->fonts[$options['fontFileName']]['isUnicode']) {
849
+ // For Unicode fonts, we need to incorporate font data into
850
+ // sub-sections that are linked from the primary font section.
851
+ // Look at o_fontGIDtoCID and o_fontDescendentCID functions
852
+ // for more information.
853
+ //
854
+ // All of this code is adapted from the excellent changes made to
855
+ // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
856
+
857
+ $toUnicodeId = ++$this->numObj;
858
+ $this->o_toUnicode($toUnicodeId, 'new');
859
+ $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
860
+
861
+ $cidFontId = ++$this->numObj;
862
+ $this->o_fontDescendentCID($cidFontId, 'new', $options);
863
+ $this->objects[$id]['info']['cidFont'] = $cidFontId;
864
+ }
865
+
866
+ // also tell the pages node about the new font
867
+ $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
868
+ break;
869
+
870
+ case 'add':
871
+ foreach ($options as $k => $v) {
872
+ switch ($k) {
873
+ case 'BaseFont':
874
+ $o['info']['name'] = $v;
875
+ break;
876
+ case 'FirstChar':
877
+ case 'LastChar':
878
+ case 'Widths':
879
+ case 'FontDescriptor':
880
+ case 'SubType':
881
+ $this->addMessage('o_font ' . $k . " : " . $v);
882
+ $o['info'][$k] = $v;
883
+ break;
884
+ }
885
+ }
886
+
887
+ // pass values down to descendent font
888
+ if (isset($o['info']['cidFont'])) {
889
+ $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
890
+ }
891
+ break;
892
+
893
+ case 'out':
894
+ if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
895
+ // For Unicode fonts, we need to incorporate font data into
896
+ // sub-sections that are linked from the primary font section.
897
+ // Look at o_fontGIDtoCID and o_fontDescendentCID functions
898
+ // for more information.
899
+ //
900
+ // All of this code is adapted from the excellent changes made to
901
+ // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
902
+
903
+ $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
904
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
905
+
906
+ // The horizontal identity mapping for 2-byte CIDs; may be used
907
+ // with CIDFonts using any Registry, Ordering, and Supplement values.
908
+ $res .= "/Encoding /Identity-H\n";
909
+ $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
910
+ $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
911
+ $res .= ">>\n";
912
+ $res .= "endobj";
913
+ } else {
914
+ $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
915
+ $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
916
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
917
+
918
+ if (isset($o['info']['encodingDictionary'])) {
919
+ // then place a reference to the dictionary
920
+ $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
921
+ } else {
922
+ if (isset($o['info']['encoding'])) {
923
+ // use the specified encoding
924
+ $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
925
+ }
926
+ }
927
+
928
+ if (isset($o['info']['FirstChar'])) {
929
+ $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
930
+ }
931
+
932
+ if (isset($o['info']['LastChar'])) {
933
+ $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
934
+ }
935
+
936
+ if (isset($o['info']['Widths'])) {
937
+ $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
938
+ }
939
+
940
+ if (isset($o['info']['FontDescriptor'])) {
941
+ $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
942
+ }
943
+
944
+ $res .= ">>\n";
945
+ $res .= "endobj";
946
+ }
947
+
948
+ return $res;
949
+ }
950
+
951
+ return null;
952
+ }
953
+
954
+ /**
955
+ * A toUnicode section, needed for unicode fonts
956
+ *
957
+ * @param $id
958
+ * @param $action
959
+ * @return null|string
960
+ */
961
+ protected function o_toUnicode($id, $action)
962
+ {
963
+ switch ($action) {
964
+ case 'new':
965
+ $this->objects[$id] = array(
966
+ 't' => 'toUnicode'
967
+ );
968
+ break;
969
+ case 'add':
970
+ break;
971
+ case 'out':
972
+ $ordering = '(UCS)';
973
+ $registry = '(Adobe)';
974
+
975
+ if ($this->encrypted) {
976
+ $this->encryptInit($id);
977
+ $ordering = $this->ARC4($ordering);
978
+ $registry = $this->ARC4($registry);
979
+ }
980
+
981
+ $stream = <<<EOT
982
+ /CIDInit /ProcSet findresource begin
983
+ 12 dict begin
984
+ begincmap
985
+ /CIDSystemInfo
986
+ <</Registry $registry
987
+ /Ordering $ordering
988
+ /Supplement 0
989
+ >> def
990
+ /CMapName /Adobe-Identity-UCS def
991
+ /CMapType 2 def
992
+ 1 begincodespacerange
993
+ <0000> <FFFF>
994
+ endcodespacerange
995
+ 1 beginbfrange
996
+ <0000> <FFFF> <0000>
997
+ endbfrange
998
+ endcmap
999
+ CMapName currentdict /CMap defineresource pop
1000
+ end
1001
+ end
1002
+ EOT;
1003
+
1004
+ $res = "\n$id 0 obj\n";
1005
+ $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1006
+ $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
1007
+
1008
+ return $res;
1009
+ }
1010
+
1011
+ return null;
1012
+ }
1013
+
1014
+ /**
1015
+ * a font descriptor, needed for including additional fonts
1016
+ *
1017
+ * @param $id
1018
+ * @param $action
1019
+ * @param string $options
1020
+ * @return null|string
1021
+ */
1022
+ protected function o_fontDescriptor($id, $action, $options = '')
1023
+ {
1024
+ if ($action !== 'new') {
1025
+ $o = &$this->objects[$id];
1026
+ }
1027
+
1028
+ switch ($action) {
1029
+ case 'new':
1030
+ $this->objects[$id] = array('t' => 'fontDescriptor', 'info' => $options);
1031
+ break;
1032
+
1033
+ case 'out':
1034
+ $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1035
+ foreach ($o['info'] as $label => $value) {
1036
+ switch ($label) {
1037
+ case 'Ascent':
1038
+ case 'CapHeight':
1039
+ case 'Descent':
1040
+ case 'Flags':
1041
+ case 'ItalicAngle':
1042
+ case 'StemV':
1043
+ case 'AvgWidth':
1044
+ case 'Leading':
1045
+ case 'MaxWidth':
1046
+ case 'MissingWidth':
1047
+ case 'StemH':
1048
+ case 'XHeight':
1049
+ case 'CharSet':
1050
+ if (mb_strlen($value, '8bit')) {
1051
+ $res .= "/$label $value\n";
1052
+ }
1053
+
1054
+ break;
1055
+ case 'FontFile':
1056
+ case 'FontFile2':
1057
+ case 'FontFile3':
1058
+ $res .= "/$label $value 0 R\n";
1059
+ break;
1060
+
1061
+ case 'FontBBox':
1062
+ $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1063
+ break;
1064
+
1065
+ case 'FontName':
1066
+ $res .= "/$label /$value\n";
1067
+ break;
1068
+ }
1069
+ }
1070
+
1071
+ $res .= ">>\nendobj";
1072
+
1073
+ return $res;
1074
+ }
1075
+
1076
+ return null;
1077
+ }
1078
+
1079
+ /**
1080
+ * the font encoding
1081
+ *
1082
+ * @param $id
1083
+ * @param $action
1084
+ * @param string $options
1085
+ * @return null|string
1086
+ */
1087
+ protected function o_fontEncoding($id, $action, $options = '')
1088
+ {
1089
+ if ($action !== 'new') {
1090
+ $o = &$this->objects[$id];
1091
+ }
1092
+
1093
+ switch ($action) {
1094
+ case 'new':
1095
+ // the options array should contain 'differences' and maybe 'encoding'
1096
+ $this->objects[$id] = array('t' => 'fontEncoding', 'info' => $options);
1097
+ break;
1098
+
1099
+ case 'out':
1100
+ $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1101
+ if (!isset($o['info']['encoding'])) {
1102
+ $o['info']['encoding'] = 'WinAnsiEncoding';
1103
+ }
1104
+
1105
+ if ($o['info']['encoding'] !== 'none') {
1106
+ $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1107
+ }
1108
+
1109
+ $res .= "/Differences \n[";
1110
+
1111
+ $onum = -100;
1112
+
1113
+ foreach ($o['info']['differences'] as $num => $label) {
1114
+ if ($num != $onum + 1) {
1115
+ // we cannot make use of consecutive numbering
1116
+ $res .= "\n$num /$label";
1117
+ } else {
1118
+ $res .= " /$label";
1119
+ }
1120
+
1121
+ $onum = $num;
1122
+ }
1123
+
1124
+ $res .= "\n]\n>>\nendobj";
1125
+
1126
+ return $res;
1127
+ }
1128
+
1129
+ return null;
1130
+ }
1131
+
1132
+ /**
1133
+ * a descendent cid font, needed for unicode fonts
1134
+ *
1135
+ * @param $id
1136
+ * @param $action
1137
+ * @param string|array $options
1138
+ * @return null|string
1139
+ */
1140
+ protected function o_fontDescendentCID($id, $action, $options = '')
1141
+ {
1142
+ if ($action !== 'new') {
1143
+ $o = &$this->objects[$id];
1144
+ }
1145
+
1146
+ switch ($action) {
1147
+ case 'new':
1148
+ $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options);
1149
+
1150
+ // we need a CID system info section
1151
+ $cidSystemInfoId = ++$this->numObj;
1152
+ $this->o_cidSystemInfo($cidSystemInfoId, 'new');
1153
+ $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1154
+
1155
+ // and a CID to GID map
1156
+ $cidToGidMapId = ++$this->numObj;
1157
+ $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1158
+ $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1159
+ break;
1160
+
1161
+ case 'add':
1162
+ foreach ($options as $k => $v) {
1163
+ switch ($k) {
1164
+ case 'BaseFont':
1165
+ $o['info']['name'] = $v;
1166
+ break;
1167
+
1168
+ case 'FirstChar':
1169
+ case 'LastChar':
1170
+ case 'MissingWidth':
1171
+ case 'FontDescriptor':
1172
+ case 'SubType':
1173
+ $this->addMessage("o_fontDescendentCID $k : $v");
1174
+ $o['info'][$k] = $v;
1175
+ break;
1176
+ }
1177
+ }
1178
+
1179
+ // pass values down to cid to gid map
1180
+ $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1181
+ break;
1182
+
1183
+ case 'out':
1184
+ $res = "\n$id 0 obj\n";
1185
+ $res .= "<</Type /Font\n";
1186
+ $res .= "/Subtype /CIDFontType2\n";
1187
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1188
+ $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1189
+ // if (isset($o['info']['FirstChar'])) {
1190
+ // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1191
+ // }
1192
+
1193
+ // if (isset($o['info']['LastChar'])) {
1194
+ // $res.= "/LastChar ".$o['info']['LastChar']."\n";
1195
+ // }
1196
+ if (isset($o['info']['FontDescriptor'])) {
1197
+ $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1198
+ }
1199
+
1200
+ if (isset($o['info']['MissingWidth'])) {
1201
+ $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1202
+ }
1203
+
1204
+ if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1205
+ $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1206
+ $w = '';
1207
+ foreach ($cid_widths as $cid => $width) {
1208
+ $w .= "$cid [$width] ";
1209
+ }
1210
+ $res .= "/W [$w]\n";
1211
+ }
1212
+
1213
+ $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1214
+ $res .= ">>\n";
1215
+ $res .= "endobj";
1216
+
1217
+ return $res;
1218
+ }
1219
+
1220
+ return null;
1221
+ }
1222
+
1223
+ /**
1224
+ * CID system info section, needed for unicode fonts
1225
+ *
1226
+ * @param $id
1227
+ * @param $action
1228
+ * @return null|string
1229
+ */
1230
+ protected function o_cidSystemInfo($id, $action)
1231
+ {
1232
+ switch ($action) {
1233
+ case 'new':
1234
+ $this->objects[$id] = array(
1235
+ 't' => 'cidSystemInfo'
1236
+ );
1237
+ break;
1238
+ case 'add':
1239
+ break;
1240
+ case 'out':
1241
+ $ordering = '(UCS)';
1242
+ $registry = '(Adobe)';
1243
+
1244
+ if ($this->encrypted) {
1245
+ $this->encryptInit($id);
1246
+ $ordering = $this->ARC4($ordering);
1247
+ $registry = $this->ARC4($registry);
1248
+ }
1249
+
1250
+
1251
+ $res = "\n$id 0 obj\n";
1252
+
1253
+ $res .= '<</Registry ' . $registry . "\n"; // A string identifying an issuer of character collections
1254
+ $res .= '/Ordering ' . $ordering . "\n"; // A string that uniquely names a character collection issued by a specific registry
1255
+ $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1256
+ $res .= ">>";
1257
+
1258
+ $res .= "\nendobj";;
1259
+
1260
+ return $res;
1261
+ }
1262
+
1263
+ return null;
1264
+ }
1265
+
1266
+ /**
1267
+ * a font glyph to character map, needed for unicode fonts
1268
+ *
1269
+ * @param $id
1270
+ * @param $action
1271
+ * @param string $options
1272
+ * @return null|string
1273
+ */
1274
+ protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1275
+ {
1276
+ if ($action !== 'new') {
1277
+ $o = &$this->objects[$id];
1278
+ }
1279
+
1280
+ switch ($action) {
1281
+ case 'new':
1282
+ $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options);
1283
+ break;
1284
+
1285
+ case 'out':
1286
+ $res = "\n$id 0 obj\n";
1287
+ $fontFileName = $o['info']['fontFileName'];
1288
+ $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1289
+
1290
+ $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1291
+ $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1292
+
1293
+ if (!$compressed && isset($o['raw'])) {
1294
+ $res .= $tmp;
1295
+ } else {
1296
+ $res .= "<<";
1297
+
1298
+ if (!$compressed && $this->compressionReady && $this->options['compression']) {
1299
+ // then implement ZLIB based compression on this content stream
1300
+ $compressed = true;
1301
+ $tmp = gzcompress($tmp, 6);
1302
+ }
1303
+ if ($compressed) {
1304
+ $res .= "\n/Filter /FlateDecode";
1305
+ }
1306
+
1307
+ if ($this->encrypted) {
1308
+ $this->encryptInit($id);
1309
+ $tmp = $this->ARC4($tmp);
1310
+ }
1311
+
1312
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1313
+ }
1314
+
1315
+ $res .= "\nendobj";
1316
+
1317
+ return $res;
1318
+ }
1319
+
1320
+ return null;
1321
+ }
1322
+
1323
+ /**
1324
+ * the document procset, solves some problems with printing to old PS printers
1325
+ *
1326
+ * @param $id
1327
+ * @param $action
1328
+ * @param string $options
1329
+ * @return null|string
1330
+ */
1331
+ protected function o_procset($id, $action, $options = '')
1332
+ {
1333
+ if ($action !== 'new') {
1334
+ $o = &$this->objects[$id];
1335
+ }
1336
+
1337
+ switch ($action) {
1338
+ case 'new':
1339
+ $this->objects[$id] = array('t' => 'procset', 'info' => array('PDF' => 1, 'Text' => 1));
1340
+ $this->o_pages($this->currentNode, 'procset', $id);
1341
+ $this->procsetObjectId = $id;
1342
+ break;
1343
+
1344
+ case 'add':
1345
+ // this is to add new items to the procset list, despite the fact that this is considered
1346
+ // obsolete, the items are required for printing to some postscript printers
1347
+ switch ($options) {
1348
+ case 'ImageB':
1349
+ case 'ImageC':
1350
+ case 'ImageI':
1351
+ $o['info'][$options] = 1;
1352
+ break;
1353
+ }
1354
+ break;
1355
+
1356
+ case 'out':
1357
+ $res = "\n$id 0 obj\n[";
1358
+ foreach ($o['info'] as $label => $val) {
1359
+ $res .= "/$label ";
1360
+ }
1361
+ $res .= "]\nendobj";
1362
+
1363
+ return $res;
1364
+ }
1365
+
1366
+ return null;
1367
+ }
1368
+
1369
+ /**
1370
+ * define the document information
1371
+ *
1372
+ * @param $id
1373
+ * @param $action
1374
+ * @param string $options
1375
+ * @return null|string
1376
+ */
1377
+ protected function o_info($id, $action, $options = '')
1378
+ {
1379
+ switch ($action) {
1380
+ case 'new':
1381
+ $this->infoObject = $id;
1382
+ $date = 'D:' . @date('Ymd');
1383
+ $this->objects[$id] = array(
1384
+ 't' => 'info',
1385
+ 'info' => array(
1386
+ 'Producer' => 'CPDF (dompdf)',
1387
+ 'CreationDate' => $date
1388
+ )
1389
+ );
1390
+ break;
1391
+ case 'Title':
1392
+ case 'Author':
1393
+ case 'Subject':
1394
+ case 'Keywords':
1395
+ case 'Creator':
1396
+ case 'Producer':
1397
+ case 'CreationDate':
1398
+ case 'ModDate':
1399
+ case 'Trapped':
1400
+ $this->objects[$id]['info'][$action] = $options;
1401
+ break;
1402
+
1403
+ case 'out':
1404
+ $encrypted = $this->encrypted;
1405
+ if ($encrypted) {
1406
+ $this->encryptInit($id);
1407
+ }
1408
+
1409
+ $res = "\n$id 0 obj\n<<\n";
1410
+ $o = &$this->objects[$id];
1411
+ foreach ($o['info'] as $k => $v) {
1412
+ $res .= "/$k (";
1413
+
1414
+ // dates must be outputted as-is, without Unicode transformations
1415
+ if ($k !== 'CreationDate' && $k !== 'ModDate') {
1416
+ $v = $this->filterText($v, true, false);
1417
+ }
1418
+
1419
+ if ($encrypted) {
1420
+ $v = $this->ARC4($v);
1421
+ }
1422
+
1423
+ $res .= $v;
1424
+ $res .= ")\n";
1425
+ }
1426
+
1427
+ $res .= ">>\nendobj";
1428
+
1429
+ return $res;
1430
+ }
1431
+
1432
+ return null;
1433
+ }
1434
+
1435
+ /**
1436
+ * an action object, used to link to URLS initially
1437
+ *
1438
+ * @param $id
1439
+ * @param $action
1440
+ * @param string $options
1441
+ * @return null|string
1442
+ */
1443
+ protected function o_action($id, $action, $options = '')
1444
+ {
1445
+ if ($action !== 'new') {
1446
+ $o = &$this->objects[$id];
1447
+ }
1448
+
1449
+ switch ($action) {
1450
+ case 'new':
1451
+ if (is_array($options)) {
1452
+ $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => $options['type']);
1453
+ } else {
1454
+ // then assume a URI action
1455
+ $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => 'URI');
1456
+ }
1457
+ break;
1458
+
1459
+ case 'out':
1460
+ if ($this->encrypted) {
1461
+ $this->encryptInit($id);
1462
+ }
1463
+
1464
+ $res = "\n$id 0 obj\n<< /Type /Action";
1465
+ switch ($o['type']) {
1466
+ case 'ilink':
1467
+ if (!isset($this->destinations[(string)$o['info']['label']])) {
1468
+ break;
1469
+ }
1470
+
1471
+ // there will be an 'label' setting, this is the name of the destination
1472
+ $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1473
+ break;
1474
+
1475
+ case 'URI':
1476
+ $res .= "\n/S /URI\n/URI (";
1477
+ if ($this->encrypted) {
1478
+ $res .= $this->filterText($this->ARC4($o['info']), false, false);
1479
+ } else {
1480
+ $res .= $this->filterText($o['info'], false, false);
1481
+ }
1482
+
1483
+ $res .= ")";
1484
+ break;
1485
+ }
1486
+
1487
+ $res .= "\n>>\nendobj";
1488
+
1489
+ return $res;
1490
+ }
1491
+
1492
+ return null;
1493
+ }
1494
+
1495
+ /**
1496
+ * an annotation object, this will add an annotation to the current page.
1497
+ * initially will support just link annotations
1498
+ *
1499
+ * @param $id
1500
+ * @param $action
1501
+ * @param string $options
1502
+ * @return null|string
1503
+ */
1504
+ protected function o_annotation($id, $action, $options = '')
1505
+ {
1506
+ if ($action !== 'new') {
1507
+ $o = &$this->objects[$id];
1508
+ }
1509
+
1510
+ switch ($action) {
1511
+ case 'new':
1512
+ // add the annotation to the current page
1513
+ $pageId = $this->currentPage;
1514
+ $this->o_page($pageId, 'annot', $id);
1515
+
1516
+ // and add the action object which is going to be required
1517
+ switch ($options['type']) {
1518
+ case 'link':
1519
+ $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
1520
+ $this->numObj++;
1521
+ $this->o_action($this->numObj, 'new', $options['url']);
1522
+ $this->objects[$id]['info']['actionId'] = $this->numObj;
1523
+ break;
1524
+
1525
+ case 'ilink':
1526
+ // this is to a named internal link
1527
+ $label = $options['label'];
1528
+ $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
1529
+ $this->numObj++;
1530
+ $this->o_action($this->numObj, 'new', array('type' => 'ilink', 'label' => $label));
1531
+ $this->objects[$id]['info']['actionId'] = $this->numObj;
1532
+ break;
1533
+ }
1534
+ break;
1535
+
1536
+ case 'out':
1537
+ $res = "\n$id 0 obj\n<< /Type /Annot";
1538
+ switch ($o['info']['type']) {
1539
+ case 'link':
1540
+ case 'ilink':
1541
+ $res .= "\n/Subtype /Link";
1542
+ break;
1543
+ }
1544
+ $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1545
+ $res .= "\n/Border [0 0 0]";
1546
+ $res .= "\n/H /I";
1547
+ $res .= "\n/Rect [ ";
1548
+
1549
+ foreach ($o['info']['rect'] as $v) {
1550
+ $res .= sprintf("%.4F ", $v);
1551
+ }
1552
+
1553
+ $res .= "]";
1554
+ $res .= "\n>>\nendobj";
1555
+
1556
+ return $res;
1557
+ }
1558
+
1559
+ return null;
1560
+ }
1561
+
1562
+ /**
1563
+ * a page object, it also creates a contents object to hold its contents
1564
+ *
1565
+ * @param $id
1566
+ * @param $action
1567
+ * @param string $options
1568
+ * @return null|string
1569
+ */
1570
+ protected function o_page($id, $action, $options = '')
1571
+ {
1572
+ if ($action !== 'new') {
1573
+ $o = &$this->objects[$id];
1574
+ }
1575
+
1576
+ switch ($action) {
1577
+ case 'new':
1578
+ $this->numPages++;
1579
+ $this->objects[$id] = array(
1580
+ 't' => 'page',
1581
+ 'info' => array(
1582
+ 'parent' => $this->currentNode,
1583
+ 'pageNum' => $this->numPages,
1584
+ 'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1585
+ )
1586
+ );
1587
+
1588
+ if (is_array($options)) {
1589
+ // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1590
+ $options['id'] = $id;
1591
+ $this->o_pages($this->currentNode, 'page', $options);
1592
+ } else {
1593
+ $this->o_pages($this->currentNode, 'page', $id);
1594
+ }
1595
+
1596
+ $this->currentPage = $id;
1597
+ //make a contents object to go with this page
1598
+ $this->numObj++;
1599
+ $this->o_contents($this->numObj, 'new', $id);
1600
+ $this->currentContents = $this->numObj;
1601
+ $this->objects[$id]['info']['contents'] = array();
1602
+ $this->objects[$id]['info']['contents'][] = $this->numObj;
1603
+
1604
+ $match = ($this->numPages % 2 ? 'odd' : 'even');
1605
+ foreach ($this->addLooseObjects as $oId => $target) {
1606
+ if ($target === 'all' || $match === $target) {
1607
+ $this->objects[$id]['info']['contents'][] = $oId;
1608
+ }
1609
+ }
1610
+ break;
1611
+
1612
+ case 'content':
1613
+ $o['info']['contents'][] = $options;
1614
+ break;
1615
+
1616
+ case 'annot':
1617
+ // add an annotation to this page
1618
+ if (!isset($o['info']['annot'])) {
1619
+ $o['info']['annot'] = array();
1620
+ }
1621
+
1622
+ // $options should contain the id of the annotation dictionary
1623
+ $o['info']['annot'][] = $options;
1624
+ break;
1625
+
1626
+ case 'out':
1627
+ $res = "\n$id 0 obj\n<< /Type /Page";
1628
+ if (isset($o['info']['mediaBox'])) {
1629
+ $tmp = $o['info']['mediaBox'];
1630
+ $res .= "\n/MediaBox [" . sprintf(
1631
+ '%.3F %.3F %.3F %.3F',
1632
+ $tmp[0],
1633
+ $tmp[1],
1634
+ $tmp[2],
1635
+ $tmp[3]
1636
+ ) . ']';
1637
+ }
1638
+ $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1639
+
1640
+ if (isset($o['info']['annot'])) {
1641
+ $res .= "\n/Annots [";
1642
+ foreach ($o['info']['annot'] as $aId) {
1643
+ $res .= " $aId 0 R";
1644
+ }
1645
+ $res .= " ]";
1646
+ }
1647
+
1648
+ $count = count($o['info']['contents']);
1649
+ if ($count == 1) {
1650
+ $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1651
+ } else {
1652
+ if ($count > 1) {
1653
+ $res .= "\n/Contents [\n";
1654
+
1655
+ // reverse the page contents so added objects are below normal content
1656
+ //foreach (array_reverse($o['info']['contents']) as $cId) {
1657
+ // Back to normal now that I've got transparency working --Benj
1658
+ foreach ($o['info']['contents'] as $cId) {
1659
+ $res .= "$cId 0 R\n";
1660
+ }
1661
+ $res .= "]";
1662
+ }
1663
+ }
1664
+
1665
+ $res .= "\n>>\nendobj";
1666
+
1667
+ return $res;
1668
+ }
1669
+
1670
+ return null;
1671
+ }
1672
+
1673
+ /**
1674
+ * the contents objects hold all of the content which appears on pages
1675
+ *
1676
+ * @param $id
1677
+ * @param $action
1678
+ * @param string|array $options
1679
+ * @return null|string
1680
+ */
1681
+ protected function o_contents($id, $action, $options = '')
1682
+ {
1683
+ if ($action !== 'new') {
1684
+ $o = &$this->objects[$id];
1685
+ }
1686
+
1687
+ switch ($action) {
1688
+ case 'new':
1689
+ $this->objects[$id] = array('t' => 'contents', 'c' => '', 'info' => array());
1690
+ if (mb_strlen($options, '8bit') && intval($options)) {
1691
+ // then this contents is the primary for a page
1692
+ $this->objects[$id]['onPage'] = $options;
1693
+ } else {
1694
+ if ($options === 'raw') {
1695
+ // then this page contains some other type of system object
1696
+ $this->objects[$id]['raw'] = 1;
1697
+ }
1698
+ }
1699
+ break;
1700
+
1701
+ case 'add':
1702
+ // add more options to the declaration
1703
+ foreach ($options as $k => $v) {
1704
+ $o['info'][$k] = $v;
1705
+ }
1706
+
1707
+ case 'out':
1708
+ $tmp = $o['c'];
1709
+ $res = "\n$id 0 obj\n";
1710
+
1711
+ if (isset($this->objects[$id]['raw'])) {
1712
+ $res .= $tmp;
1713
+ } else {
1714
+ $res .= "<<";
1715
+ if ($this->compressionReady && $this->options['compression']) {
1716
+ // then implement ZLIB based compression on this content stream
1717
+ $res .= " /Filter /FlateDecode";
1718
+ $tmp = gzcompress($tmp, 6);
1719
+ }
1720
+
1721
+ if ($this->encrypted) {
1722
+ $this->encryptInit($id);
1723
+ $tmp = $this->ARC4($tmp);
1724
+ }
1725
+
1726
+ foreach ($o['info'] as $k => $v) {
1727
+ $res .= "\n/$k $v";
1728
+ }
1729
+
1730
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
1731
+ }
1732
+
1733
+ $res .= "\nendobj";
1734
+
1735
+ return $res;
1736
+ }
1737
+
1738
+ return null;
1739
+ }
1740
+
1741
+ /**
1742
+ * @param $id
1743
+ * @param $action
1744
+ * @return string|null
1745
+ */
1746
+ protected function o_embedjs($id, $action)
1747
+ {
1748
+ switch ($action) {
1749
+ case 'new':
1750
+ $this->objects[$id] = array(
1751
+ 't' => 'embedjs',
1752
+ 'info' => array(
1753
+ 'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
1754
+ )
1755
+ );
1756
+ break;
1757
+
1758
+ case 'out':
1759
+ $o = &$this->objects[$id];
1760
+ $res = "\n$id 0 obj\n<< ";
1761
+ foreach ($o['info'] as $k => $v) {
1762
+ $res .= "\n/$k $v";
1763
+ }
1764
+ $res .= "\n>>\nendobj";
1765
+
1766
+ return $res;
1767
+ }
1768
+
1769
+ return null;
1770
+ }
1771
+
1772
+ /**
1773
+ * @param $id
1774
+ * @param $action
1775
+ * @param string $code
1776
+ * @return null|string
1777
+ */
1778
+ protected function o_javascript($id, $action, $code = '')
1779
+ {
1780
+ switch ($action) {
1781
+ case 'new':
1782
+ $this->objects[$id] = array(
1783
+ 't' => 'javascript',
1784
+ 'info' => array(
1785
+ 'S' => '/JavaScript',
1786
+ 'JS' => '(' . $this->filterText($code, true, false) . ')',
1787
+ )
1788
+ );
1789
+ break;
1790
+
1791
+ case 'out':
1792
+ $o = &$this->objects[$id];
1793
+ $res = "\n$id 0 obj\n<< ";
1794
+
1795
+ foreach ($o['info'] as $k => $v) {
1796
+ $res .= "\n/$k $v";
1797
+ }
1798
+ $res .= "\n>>\nendobj";
1799
+
1800
+ return $res;
1801
+ }
1802
+
1803
+ return null;
1804
+ }
1805
+
1806
+ /**
1807
+ * an image object, will be an XObject in the document, includes description and data
1808
+ *
1809
+ * @param $id
1810
+ * @param $action
1811
+ * @param string $options
1812
+ * @return null|string
1813
+ */
1814
+ protected function o_image($id, $action, $options = '')
1815
+ {
1816
+ switch ($action) {
1817
+ case 'new':
1818
+ // make the new object
1819
+ $this->objects[$id] = array('t' => 'image', 'data' => &$options['data'], 'info' => array());
1820
+
1821
+ $info =& $this->objects[$id]['info'];
1822
+
1823
+ $info['Type'] = '/XObject';
1824
+ $info['Subtype'] = '/Image';
1825
+ $info['Width'] = $options['iw'];
1826
+ $info['Height'] = $options['ih'];
1827
+
1828
+ if (isset($options['masked']) && $options['masked']) {
1829
+ $info['SMask'] = ($this->numObj - 1) . ' 0 R';
1830
+ }
1831
+
1832
+ if (!isset($options['type']) || $options['type'] === 'jpg') {
1833
+ if (!isset($options['channels'])) {
1834
+ $options['channels'] = 3;
1835
+ }
1836
+
1837
+ switch ($options['channels']) {
1838
+ case 1:
1839
+ $info['ColorSpace'] = '/DeviceGray';
1840
+ break;
1841
+ case 4:
1842
+ $info['ColorSpace'] = '/DeviceCMYK';
1843
+ break;
1844
+ default:
1845
+ $info['ColorSpace'] = '/DeviceRGB';
1846
+ break;
1847
+ }
1848
+
1849
+ if ($info['ColorSpace'] === '/DeviceCMYK') {
1850
+ $info['Decode'] = '[1 0 1 0 1 0 1 0]';
1851
+ }
1852
+
1853
+ $info['Filter'] = '/DCTDecode';
1854
+ $info['BitsPerComponent'] = 8;
1855
+ } else {
1856
+ if ($options['type'] === 'png') {
1857
+ $info['Filter'] = '/FlateDecode';
1858
+ $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
1859
+
1860
+ if ($options['isMask']) {
1861
+ $info['ColorSpace'] = '/DeviceGray';
1862
+ } else {
1863
+ if (mb_strlen($options['pdata'], '8bit')) {
1864
+ $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
1865
+ $this->numObj++;
1866
+ $this->o_contents($this->numObj, 'new');
1867
+ $this->objects[$this->numObj]['c'] = $options['pdata'];
1868
+ $tmp .= $this->numObj . ' 0 R';
1869
+ $tmp .= ' ]';
1870
+ $info['ColorSpace'] = $tmp;
1871
+
1872
+ if (isset($options['transparency'])) {
1873
+ $transparency = $options['transparency'];
1874
+ switch ($transparency['type']) {
1875
+ case 'indexed':
1876
+ $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
1877
+ $info['Mask'] = $tmp;
1878
+ break;
1879
+
1880
+ case 'color-key':
1881
+ $tmp = ' [ ' .
1882
+ $transparency['r'] . ' ' . $transparency['r'] .
1883
+ $transparency['g'] . ' ' . $transparency['g'] .
1884
+ $transparency['b'] . ' ' . $transparency['b'] .
1885
+ ' ] ';
1886
+ $info['Mask'] = $tmp;
1887
+ break;
1888
+ }
1889
+ }
1890
+ } else {
1891
+ if (isset($options['transparency'])) {
1892
+ $transparency = $options['transparency'];
1893
+
1894
+ switch ($transparency['type']) {
1895
+ case 'indexed':
1896
+ $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
1897
+ $info['Mask'] = $tmp;
1898
+ break;
1899
+
1900
+ case 'color-key':
1901
+ $tmp = ' [ ' .
1902
+ $transparency['r'] . ' ' . $transparency['r'] . ' ' .
1903
+ $transparency['g'] . ' ' . $transparency['g'] . ' ' .
1904
+ $transparency['b'] . ' ' . $transparency['b'] .
1905
+ ' ] ';
1906
+ $info['Mask'] = $tmp;
1907
+ break;
1908
+ }
1909
+ }
1910
+ $info['ColorSpace'] = '/' . $options['color'];
1911
+ }
1912
+ }
1913
+
1914
+ $info['BitsPerComponent'] = $options['bitsPerComponent'];
1915
+ }
1916
+ }
1917
+
1918
+ // assign it a place in the named resource dictionary as an external object, according to
1919
+ // the label passed in with it.
1920
+ $this->o_pages($this->currentNode, 'xObject', array('label' => $options['label'], 'objNum' => $id));
1921
+
1922
+ // also make sure that we have the right procset object for it.
1923
+ $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
1924
+ break;
1925
+
1926
+ case 'out':
1927
+ $o = &$this->objects[$id];
1928
+ $tmp = &$o['data'];
1929
+ $res = "\n$id 0 obj\n<<";
1930
+
1931
+ foreach ($o['info'] as $k => $v) {
1932
+ $res .= "\n/$k $v";
1933
+ }
1934
+
1935
+ if ($this->encrypted) {
1936
+ $this->encryptInit($id);
1937
+ $tmp = $this->ARC4($tmp);
1938
+ }
1939
+
1940
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
1941
+
1942
+ return $res;
1943
+ }
1944
+
1945
+ return null;
1946
+ }
1947
+
1948
+ /**
1949
+ * graphics state object
1950
+ *
1951
+ * @param $id
1952
+ * @param $action
1953
+ * @param string $options
1954
+ * @return null|string
1955
+ */
1956
+ protected function o_extGState($id, $action, $options = "")
1957
+ {
1958
+ static $valid_params = array(
1959
+ "LW",
1960
+ "LC",
1961
+ "LC",
1962
+ "LJ",
1963
+ "ML",
1964
+ "D",
1965
+ "RI",
1966
+ "OP",
1967
+ "op",
1968
+ "OPM",
1969
+ "Font",
1970
+ "BG",
1971
+ "BG2",
1972
+ "UCR",
1973
+ "TR",
1974
+ "TR2",
1975
+ "HT",
1976
+ "FL",
1977
+ "SM",
1978
+ "SA",
1979
+ "BM",
1980
+ "SMask",
1981
+ "CA",
1982
+ "ca",
1983
+ "AIS",
1984
+ "TK"
1985
+ );
1986
+
1987
+ switch ($action) {
1988
+ case "new":
1989
+ $this->objects[$id] = array('t' => 'extGState', 'info' => $options);
1990
+
1991
+ // Tell the pages about the new resource
1992
+ $this->numStates++;
1993
+ $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates));
1994
+ break;
1995
+
1996
+ case "out":
1997
+ $o = &$this->objects[$id];
1998
+ $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
1999
+
2000
+ foreach ($o["info"] as $k => $v) {
2001
+ if (!in_array($k, $valid_params)) {
2002
+ continue;
2003
+ }
2004
+ $res .= "/$k $v\n";
2005
+ }
2006
+
2007
+ $res .= ">>\nendobj";
2008
+
2009
+ return $res;
2010
+ }
2011
+
2012
+ return null;
2013
+ }
2014
+
2015
+ /**
2016
+ * encryption object.
2017
+ *
2018
+ * @param $id
2019
+ * @param $action
2020
+ * @param string $options
2021
+ * @return string|null
2022
+ */
2023
+ protected function o_encryption($id, $action, $options = '')
2024
+ {
2025
+ switch ($action) {
2026
+ case 'new':
2027
+ // make the new object
2028
+ $this->objects[$id] = array('t' => 'encryption', 'info' => $options);
2029
+ $this->arc4_objnum = $id;
2030
+ break;
2031
+
2032
+ case 'keys':
2033
+ // figure out the additional parameters required
2034
+ $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2035
+ . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2036
+ . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2037
+ . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2038
+
2039
+ $info = $this->objects[$id]['info'];
2040
+
2041
+ $len = mb_strlen($info['owner'], '8bit');
2042
+
2043
+ if ($len > 32) {
2044
+ $owner = substr($info['owner'], 0, 32);
2045
+ } else {
2046
+ if ($len < 32) {
2047
+ $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2048
+ } else {
2049
+ $owner = $info['owner'];
2050
+ }
2051
+ }
2052
+
2053
+ $len = mb_strlen($info['user'], '8bit');
2054
+ if ($len > 32) {
2055
+ $user = substr($info['user'], 0, 32);
2056
+ } else {
2057
+ if ($len < 32) {
2058
+ $user = $info['user'] . substr($pad, 0, 32 - $len);
2059
+ } else {
2060
+ $user = $info['user'];
2061
+ }
2062
+ }
2063
+
2064
+ $tmp = $this->md5_16($owner);
2065
+ $okey = substr($tmp, 0, 5);
2066
+ $this->ARC4_init($okey);
2067
+ $ovalue = $this->ARC4($user);
2068
+ $this->objects[$id]['info']['O'] = $ovalue;
2069
+
2070
+ // now make the u value, phew.
2071
+ $tmp = $this->md5_16(
2072
+ $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2073
+ );
2074
+
2075
+ $ukey = substr($tmp, 0, 5);
2076
+ $this->ARC4_init($ukey);
2077
+ $this->encryptionKey = $ukey;
2078
+ $this->encrypted = true;
2079
+ $uvalue = $this->ARC4($pad);
2080
+ $this->objects[$id]['info']['U'] = $uvalue;
2081
+ // initialize the arc4 array
2082
+ break;
2083
+
2084
+ case 'out':
2085
+ $o = &$this->objects[$id];
2086
+
2087
+ $res = "\n$id 0 obj\n<<";
2088
+ $res .= "\n/Filter /Standard";
2089
+ $res .= "\n/V 1";
2090
+ $res .= "\n/R 2";
2091
+ $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2092
+ $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2093
+ // and the p-value needs to be converted to account for the twos-complement approach
2094
+ $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2095
+ $res .= "\n/P " . ($o['info']['p']);
2096
+ $res .= "\n>>\nendobj";
2097
+
2098
+ return $res;
2099
+ }
2100
+
2101
+ return null;
2102
+ }
2103
+
2104
+ /**
2105
+ * ARC4 functions
2106
+ * A series of function to implement ARC4 encoding in PHP
2107
+ */
2108
+
2109
+ /**
2110
+ * calculate the 16 byte version of the 128 bit md5 digest of the string
2111
+ *
2112
+ * @param $string
2113
+ * @return string
2114
+ */
2115
+ function md5_16($string)
2116
+ {
2117
+ $tmp = md5($string);
2118
+ $out = '';
2119
+ for ($i = 0; $i <= 30; $i = $i + 2) {
2120
+ $out .= chr(hexdec(substr($tmp, $i, 2)));
2121
+ }
2122
+
2123
+ return $out;
2124
+ }
2125
+
2126
+ /**
2127
+ * initialize the encryption for processing a particular object
2128
+ *
2129
+ * @param $id
2130
+ */
2131
+ function encryptInit($id)
2132
+ {
2133
+ $tmp = $this->encryptionKey;
2134
+ $hex = dechex($id);
2135
+ if (mb_strlen($hex, '8bit') < 6) {
2136
+ $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2137
+ }
2138
+ $tmp .= chr(hexdec(substr($hex, 4, 2)))
2139
+ . chr(hexdec(substr($hex, 2, 2)))
2140
+ . chr(hexdec(substr($hex, 0, 2)))
2141
+ . chr(0)
2142
+ . chr(0)
2143
+ ;
2144
+ $key = $this->md5_16($tmp);
2145
+ $this->ARC4_init(substr($key, 0, 10));
2146
+ }
2147
+
2148
+ /**
2149
+ * initialize the ARC4 encryption
2150
+ *
2151
+ * @param string $key
2152
+ */
2153
+ function ARC4_init($key = '')
2154
+ {
2155
+ $this->arc4 = '';
2156
+
2157
+ // setup the control array
2158
+ if (mb_strlen($key, '8bit') == 0) {
2159
+ return;
2160
+ }
2161
+
2162
+ $k = '';
2163
+ while (mb_strlen($k, '8bit') < 256) {
2164
+ $k .= $key;
2165
+ }
2166
+
2167
+ $k = substr($k, 0, 256);
2168
+ for ($i = 0; $i < 256; $i++) {
2169
+ $this->arc4 .= chr($i);
2170
+ }
2171
+
2172
+ $j = 0;
2173
+
2174
+ for ($i = 0; $i < 256; $i++) {
2175
+ $t = $this->arc4[$i];
2176
+ $j = ($j + ord($t) + ord($k[$i])) % 256;
2177
+ $this->arc4[$i] = $this->arc4[$j];
2178
+ $this->arc4[$j] = $t;
2179
+ }
2180
+ }
2181
+
2182
+ /**
2183
+ * ARC4 encrypt a text string
2184
+ *
2185
+ * @param $text
2186
+ * @return string
2187
+ */
2188
+ function ARC4($text)
2189
+ {
2190
+ $len = mb_strlen($text, '8bit');
2191
+ $a = 0;
2192
+ $b = 0;
2193
+ $c = $this->arc4;
2194
+ $out = '';
2195
+ for ($i = 0; $i < $len; $i++) {
2196
+ $a = ($a + 1) % 256;
2197
+ $t = $c[$a];
2198
+ $b = ($b + ord($t)) % 256;
2199
+ $c[$a] = $c[$b];
2200
+ $c[$b] = $t;
2201
+ $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
2202
+ $out .= chr(ord($text[$i]) ^ $k);
2203
+ }
2204
+
2205
+ return $out;
2206
+ }
2207
+
2208
+ /**
2209
+ * functions which can be called to adjust or add to the document
2210
+ */
2211
+
2212
+ /**
2213
+ * add a link in the document to an external URL
2214
+ *
2215
+ * @param $url
2216
+ * @param $x0
2217
+ * @param $y0
2218
+ * @param $x1
2219
+ * @param $y1
2220
+ */
2221
+ function addLink($url, $x0, $y0, $x1, $y1)
2222
+ {
2223
+ $this->numObj++;
2224
+ $info = array('type' => 'link', 'url' => $url, 'rect' => array($x0, $y0, $x1, $y1));
2225
+ $this->o_annotation($this->numObj, 'new', $info);
2226
+ }
2227
+
2228
+ /**
2229
+ * add a link in the document to an internal destination (ie. within the document)
2230
+ *
2231
+ * @param $label
2232
+ * @param $x0
2233
+ * @param $y0
2234
+ * @param $x1
2235
+ * @param $y1
2236
+ */
2237
+ function addInternalLink($label, $x0, $y0, $x1, $y1)
2238
+ {
2239
+ $this->numObj++;
2240
+ $info = array('type' => 'ilink', 'label' => $label, 'rect' => array($x0, $y0, $x1, $y1));
2241
+ $this->o_annotation($this->numObj, 'new', $info);
2242
+ }
2243
+
2244
+ /**
2245
+ * set the encryption of the document
2246
+ * can be used to turn it on and/or set the passwords which it will have.
2247
+ * also the functions that the user will have are set here, such as print, modify, add
2248
+ *
2249
+ * @param string $userPass
2250
+ * @param string $ownerPass
2251
+ * @param array $pc
2252
+ */
2253
+ function setEncryption($userPass = '', $ownerPass = '', $pc = array())
2254
+ {
2255
+ $p = bindec("11000000");
2256
+
2257
+ $options = array('print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32);
2258
+
2259
+ foreach ($pc as $k => $v) {
2260
+ if ($v && isset($options[$k])) {
2261
+ $p += $options[$k];
2262
+ } else {
2263
+ if (isset($options[$v])) {
2264
+ $p += $options[$v];
2265
+ }
2266
+ }
2267
+ }
2268
+
2269
+ // implement encryption on the document
2270
+ if ($this->arc4_objnum == 0) {
2271
+ // then the block does not exist already, add it.
2272
+ $this->numObj++;
2273
+ if (mb_strlen($ownerPass) == 0) {
2274
+ $ownerPass = $userPass;
2275
+ }
2276
+
2277
+ $this->o_encryption($this->numObj, 'new', array('user' => $userPass, 'owner' => $ownerPass, 'p' => $p));
2278
+ }
2279
+ }
2280
+
2281
+ /**
2282
+ * should be used for internal checks, not implemented as yet
2283
+ */
2284
+ function checkAllHere()
2285
+ {
2286
+ }
2287
+
2288
+ /**
2289
+ * return the pdf stream as a string returned from the function
2290
+ *
2291
+ * @param bool $debug
2292
+ * @return string
2293
+ */
2294
+ function output($debug = false)
2295
+ {
2296
+ if ($debug) {
2297
+ // turn compression off
2298
+ $this->options['compression'] = false;
2299
+ }
2300
+
2301
+ if ($this->javascript) {
2302
+ $this->numObj++;
2303
+
2304
+ $js_id = $this->numObj;
2305
+ $this->o_embedjs($js_id, 'new');
2306
+ $this->o_javascript(++$this->numObj, 'new', $this->javascript);
2307
+
2308
+ $id = $this->catalogId;
2309
+
2310
+ $this->o_catalog($id, 'javascript', $js_id);
2311
+ }
2312
+
2313
+ if ($this->fileIdentifier === '') {
2314
+ $tmp = implode('', $this->objects[$this->infoObject]['info']);
2315
+ $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
2316
+ }
2317
+
2318
+ if ($this->arc4_objnum) {
2319
+ $this->o_encryption($this->arc4_objnum, 'keys');
2320
+ $this->ARC4_init($this->encryptionKey);
2321
+ }
2322
+
2323
+ $this->checkAllHere();
2324
+
2325
+ $xref = array();
2326
+ $content = '%PDF-1.3';
2327
+ $pos = mb_strlen($content, '8bit');
2328
+
2329
+ foreach ($this->objects as $k => $v) {
2330
+ $tmp = 'o_' . $v['t'];
2331
+ $cont = $this->$tmp($k, 'out');
2332
+ $content .= $cont;
2333
+ $xref[] = $pos + 1; //+1 to account for \n at the start of each object
2334
+ $pos += mb_strlen($cont, '8bit');
2335
+ }
2336
+
2337
+ $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
2338
+
2339
+ foreach ($xref as $p) {
2340
+ $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
2341
+ }
2342
+
2343
+ $content .= "trailer\n<<\n" .
2344
+ '/Size ' . (count($xref) + 1) . "\n" .
2345
+ '/Root 1 0 R' . "\n" .
2346
+ '/Info ' . $this->infoObject . " 0 R\n"
2347
+ ;
2348
+
2349
+ // if encryption has been applied to this document then add the marker for this dictionary
2350
+ if ($this->arc4_objnum > 0) {
2351
+ $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
2352
+ }
2353
+
2354
+ $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
2355
+
2356
+ // account for \n added at start of xref table
2357
+ $pos++;
2358
+
2359
+ $content .= ">>\nstartxref\n$pos\n%%EOF\n";
2360
+
2361
+ return $content;
2362
+ }
2363
+
2364
+ /**
2365
+ * initialize a new document
2366
+ * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
2367
+ * this function is called automatically by the constructor function
2368
+ *
2369
+ * @param array $pageSize
2370
+ */
2371
+ private function newDocument($pageSize = array(0, 0, 612, 792))
2372
+ {
2373
+ $this->numObj = 0;
2374
+ $this->objects = array();
2375
+
2376
+ $this->numObj++;
2377
+ $this->o_catalog($this->numObj, 'new');
2378
+
2379
+ $this->numObj++;
2380
+ $this->o_outlines($this->numObj, 'new');
2381
+
2382
+ $this->numObj++;
2383
+ $this->o_pages($this->numObj, 'new');
2384
+
2385
+ $this->o_pages($this->numObj, 'mediaBox', $pageSize);
2386
+ $this->currentNode = 3;
2387
+
2388
+ $this->numObj++;
2389
+ $this->o_procset($this->numObj, 'new');
2390
+
2391
+ $this->numObj++;
2392
+ $this->o_info($this->numObj, 'new');
2393
+
2394
+ $this->numObj++;
2395
+ $this->o_page($this->numObj, 'new');
2396
+
2397
+ // need to store the first page id as there is no way to get it to the user during
2398
+ // startup
2399
+ $this->firstPageId = $this->currentContents;
2400
+ }
2401
+
2402
+ /**
2403
+ * open the font file and return a php structure containing it.
2404
+ * first check if this one has been done before and saved in a form more suited to php
2405
+ * note that if a php serialized version does not exist it will try and make one, but will
2406
+ * require write access to the directory to do it... it is MUCH faster to have these serialized
2407
+ * files.
2408
+ *
2409
+ * @param $font
2410
+ */
2411
+ private function openFont($font)
2412
+ {
2413
+ // assume that $font contains the path and file but not the extension
2414
+ $name = basename($font);
2415
+ $dir = dirname($font) . '/';
2416
+
2417
+ $fontcache = $this->fontcache;
2418
+ if ($fontcache == '') {
2419
+ $fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\");
2420
+ }
2421
+
2422
+ //$name filename without folder and extension of font metrics
2423
+ //$dir folder of font metrics
2424
+ //$fontcache folder of runtime created php serialized version of font metrics.
2425
+ // If this is not given, the same folder as the font metrics will be used.
2426
+ // Storing and reusing serialized versions improves speed much
2427
+
2428
+ $this->addMessage("openFont: $font - $name");
2429
+
2430
+ if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
2431
+ $metrics_name = "$name.afm";
2432
+ } else {
2433
+ $metrics_name = "$name.ufm";
2434
+ }
2435
+
2436
+ $cache_name = "$metrics_name.php";
2437
+ $this->addMessage("metrics: $metrics_name, cache: $cache_name");
2438
+
2439
+ if (file_exists($fontcache . '/' . $cache_name)) {
2440
+ $this->addMessage("openFont: php file exists $fontcache/$cache_name");
2441
+ $this->fonts[$font] = require($fontcache . '/' . $cache_name);
2442
+
2443
+ if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
2444
+ // if the font file is old, then clear it out and prepare for re-creation
2445
+ $this->addMessage('openFont: clear out, make way for new version.');
2446
+ $this->fonts[$font] = null;
2447
+ unset($this->fonts[$font]);
2448
+ }
2449
+ } else {
2450
+ $old_cache_name = "php_$metrics_name";
2451
+ if (file_exists($fontcache . '/' . $old_cache_name)) {
2452
+ $this->addMessage(
2453
+ "openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
2454
+ );
2455
+ $old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
2456
+ file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
2457
+
2458
+ $this->openFont($font);
2459
+ return;
2460
+ }
2461
+ }
2462
+
2463
+ if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
2464
+ // then rebuild the php_<font>.afm file from the <font>.afm file
2465
+ $this->addMessage("openFont: build php file from $dir$metrics_name");
2466
+ $data = array();
2467
+
2468
+ // 20 => 'space'
2469
+ $data['codeToName'] = array();
2470
+
2471
+ // Since we're not going to enable Unicode for the core fonts we need to use a font-based
2472
+ // setting for Unicode support rather than a global setting.
2473
+ $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
2474
+
2475
+ $cidtogid = '';
2476
+ if ($data['isUnicode']) {
2477
+ $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
2478
+ }
2479
+
2480
+ $file = file($dir . $metrics_name);
2481
+
2482
+ foreach ($file as $rowA) {
2483
+ $row = trim($rowA);
2484
+ $pos = strpos($row, ' ');
2485
+
2486
+ if ($pos) {
2487
+ // then there must be some keyword
2488
+ $key = substr($row, 0, $pos);
2489
+ switch ($key) {
2490
+ case 'FontName':
2491
+ case 'FullName':
2492
+ case 'FamilyName':
2493
+ case 'PostScriptName':
2494
+ case 'Weight':
2495
+ case 'ItalicAngle':
2496
+ case 'IsFixedPitch':
2497
+ case 'CharacterSet':
2498
+ case 'UnderlinePosition':
2499
+ case 'UnderlineThickness':
2500
+ case 'Version':
2501
+ case 'EncodingScheme':
2502
+ case 'CapHeight':
2503
+ case 'XHeight':
2504
+ case 'Ascender':
2505
+ case 'Descender':
2506
+ case 'StdHW':
2507
+ case 'StdVW':
2508
+ case 'StartCharMetrics':
2509
+ case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big.
2510
+ $data[$key] = trim(substr($row, $pos));
2511
+ break;
2512
+
2513
+ case 'FontBBox':
2514
+ $data[$key] = explode(' ', trim(substr($row, $pos)));
2515
+ break;
2516
+
2517
+ //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
2518
+ case 'C': // Found in AFM files
2519
+ $bits = explode(';', trim($row));
2520
+ $dtmp = array();
2521
+
2522
+ foreach ($bits as $bit) {
2523
+ $bits2 = explode(' ', trim($bit));
2524
+ if (mb_strlen($bits2[0], '8bit') == 0) {
2525
+ continue;
2526
+ }
2527
+
2528
+ if (count($bits2) > 2) {
2529
+ $dtmp[$bits2[0]] = array();
2530
+ for ($i = 1; $i < count($bits2); $i++) {
2531
+ $dtmp[$bits2[0]][] = $bits2[$i];
2532
+ }
2533
+ } else {
2534
+ if (count($bits2) == 2) {
2535
+ $dtmp[$bits2[0]] = $bits2[1];
2536
+ }
2537
+ }
2538
+ }
2539
+
2540
+ $c = (int)$dtmp['C'];
2541
+ $n = $dtmp['N'];
2542
+ $width = floatval($dtmp['WX']);
2543
+
2544
+ if ($c >= 0) {
2545
+ if ($c != hexdec($n)) {
2546
+ $data['codeToName'][$c] = $n;
2547
+ }
2548
+ $data['C'][$c] = $width;
2549
+ } else {
2550
+ $data['C'][$n] = $width;
2551
+ }
2552
+
2553
+ if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') {
2554
+ $data['MissingWidth'] = $width;
2555
+ }
2556
+
2557
+ break;
2558
+
2559
+ // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
2560
+ case 'U': // Found in UFM files
2561
+ if (!$data['isUnicode']) {
2562
+ break;
2563
+ }
2564
+
2565
+ $bits = explode(';', trim($row));
2566
+ $dtmp = array();
2567
+
2568
+ foreach ($bits as $bit) {
2569
+ $bits2 = explode(' ', trim($bit));
2570
+ if (mb_strlen($bits2[0], '8bit') === 0) {
2571
+ continue;
2572
+ }
2573
+
2574
+ if (count($bits2) > 2) {
2575
+ $dtmp[$bits2[0]] = array();
2576
+ for ($i = 1; $i < count($bits2); $i++) {
2577
+ $dtmp[$bits2[0]][] = $bits2[$i];
2578
+ }
2579
+ } else {
2580
+ if (count($bits2) == 2) {
2581
+ $dtmp[$bits2[0]] = $bits2[1];
2582
+ }
2583
+ }
2584
+ }
2585
+
2586
+ $c = (int)$dtmp['U'];
2587
+ $n = $dtmp['N'];
2588
+ $glyph = $dtmp['G'];
2589
+ $width = floatval($dtmp['WX']);
2590
+
2591
+ if ($c >= 0) {
2592
+ // Set values in CID to GID map
2593
+ if ($c >= 0 && $c < 0xFFFF && $glyph) {
2594
+ $cidtogid[$c * 2] = chr($glyph >> 8);
2595
+ $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
2596
+ }
2597
+
2598
+ if ($c != hexdec($n)) {
2599
+ $data['codeToName'][$c] = $n;
2600
+ }
2601
+ $data['C'][$c] = $width;
2602
+ } else {
2603
+ $data['C'][$n] = $width;
2604
+ }
2605
+
2606
+ if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') {
2607
+ $data['MissingWidth'] = $width;
2608
+ }
2609
+
2610
+ break;
2611
+
2612
+ case 'KPX':
2613
+ break; // don't include them as they are not used yet
2614
+ //KPX Adieresis yacute -40
2615
+ /*$bits = explode(' ', trim($row));
2616
+ $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
2617
+ break;*/
2618
+ }
2619
+ }
2620
+ }
2621
+
2622
+ if ($this->compressionReady && $this->options['compression']) {
2623
+ // then implement ZLIB based compression on CIDtoGID string
2624
+ $data['CIDtoGID_Compressed'] = true;
2625
+ $cidtogid = gzcompress($cidtogid, 6);
2626
+ }
2627
+ $data['CIDtoGID'] = base64_encode($cidtogid);
2628
+ $data['_version_'] = $this->fontcacheVersion;
2629
+ $this->fonts[$font] = $data;
2630
+
2631
+ //Because of potential trouble with php safe mode, expect that the folder already exists.
2632
+ //If not existing, this will hit performance because of missing cached results.
2633
+ if (is_dir($fontcache) && is_writable($fontcache)) {
2634
+ file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
2635
+ }
2636
+ $data = null;
2637
+ }
2638
+
2639
+ if (!isset($this->fonts[$font])) {
2640
+ $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
2641
+ }
2642
+
2643
+ //pre_r($this->messages);
2644
+ }
2645
+
2646
+ /**
2647
+ * if the font is not loaded then load it and make the required object
2648
+ * else just make it the current font
2649
+ * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
2650
+ * note that encoding='none' will need to be used for symbolic fonts
2651
+ * and 'differences' => an array of mappings between numbers 0->255 and character names.
2652
+ *
2653
+ * @param $fontName
2654
+ * @param string $encoding
2655
+ * @param bool $set
2656
+ * @return int
2657
+ */
2658
+ function selectFont($fontName, $encoding = '', $set = true)
2659
+ {
2660
+ $ext = substr($fontName, -4);
2661
+ if ($ext === '.afm' || $ext === '.ufm') {
2662
+ $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
2663
+ }
2664
+
2665
+ if (!isset($this->fonts[$fontName])) {
2666
+ $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
2667
+
2668
+ // load the file
2669
+ $this->openFont($fontName);
2670
+
2671
+ if (isset($this->fonts[$fontName])) {
2672
+ $this->numObj++;
2673
+ $this->numFonts++;
2674
+
2675
+ $font = &$this->fonts[$fontName];
2676
+
2677
+ $name = basename($fontName);
2678
+ $dir = dirname($fontName) . '/';
2679
+ $options = array('name' => $name, 'fontFileName' => $fontName);
2680
+
2681
+ if (is_array($encoding)) {
2682
+ // then encoding and differences might be set
2683
+ if (isset($encoding['encoding'])) {
2684
+ $options['encoding'] = $encoding['encoding'];
2685
+ }
2686
+
2687
+ if (isset($encoding['differences'])) {
2688
+ $options['differences'] = $encoding['differences'];
2689
+ }
2690
+ } else {
2691
+ if (mb_strlen($encoding, '8bit')) {
2692
+ // then perhaps only the encoding has been set
2693
+ $options['encoding'] = $encoding;
2694
+ }
2695
+ }
2696
+
2697
+ $fontObj = $this->numObj;
2698
+ $this->o_font($this->numObj, 'new', $options);
2699
+ $font['fontNum'] = $this->numFonts;
2700
+
2701
+ // if this is a '.afm' font, and there is a '.pfa' file to go with it (as there
2702
+ // should be for all non-basic fonts), then load it into an object and put the
2703
+ // references into the font object
2704
+ $basefile = $fontName;
2705
+
2706
+ $fbtype = '';
2707
+ if (file_exists("$basefile.ttf")) {
2708
+ $fbtype = 'ttf';
2709
+ } elseif (file_exists("$basefile.TTF")) {
2710
+ $fbtype = 'TTF';
2711
+ } elseif (file_exists("$basefile.pfb")) {
2712
+ $fbtype = 'pfb';
2713
+ } elseif (file_exists("$basefile.PFB")) {
2714
+ $fbtype = 'PFB';
2715
+ }
2716
+
2717
+ $fbfile = "$basefile.$fbtype";
2718
+
2719
+ // $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
2720
+ // $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
2721
+ $this->addMessage('selectFont: checking for - ' . $fbfile);
2722
+
2723
+ // OAR - I don't understand this old check
2724
+ // if (substr($fontName, -4) === '.afm' && strlen($fbtype)) {
2725
+ if ($fbtype) {
2726
+ $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
2727
+ // $fontObj = $this->numObj;
2728
+ $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
2729
+
2730
+ // find the array of font widths, and put that into an object.
2731
+ $firstChar = -1;
2732
+ $lastChar = 0;
2733
+ $widths = array();
2734
+ $cid_widths = array();
2735
+
2736
+ foreach ($font['C'] as $num => $d) {
2737
+ if (intval($num) > 0 || $num == '0') {
2738
+ if (!$font['isUnicode']) {
2739
+ // With Unicode, widths array isn't used
2740
+ if ($lastChar > 0 && $num > $lastChar + 1) {
2741
+ for ($i = $lastChar + 1; $i < $num; $i++) {
2742
+ $widths[] = 0;
2743
+ }
2744
+ }
2745
+ }
2746
+
2747
+ $widths[] = $d;
2748
+
2749
+ if ($font['isUnicode']) {
2750
+ $cid_widths[$num] = $d;
2751
+ }
2752
+
2753
+ if ($firstChar == -1) {
2754
+ $firstChar = $num;
2755
+ }
2756
+
2757
+ $lastChar = $num;
2758
+ }
2759
+ }
2760
+
2761
+ // also need to adjust the widths for the differences array
2762
+ if (isset($options['differences'])) {
2763
+ foreach ($options['differences'] as $charNum => $charName) {
2764
+ if ($charNum > $lastChar) {
2765
+ if (!$font['isUnicode']) {
2766
+ // With Unicode, widths array isn't used
2767
+ for ($i = $lastChar + 1; $i <= $charNum; $i++) {
2768
+ $widths[] = 0;
2769
+ }
2770
+ }
2771
+
2772
+ $lastChar = $charNum;
2773
+ }
2774
+
2775
+ if (isset($font['C'][$charName])) {
2776
+ $widths[$charNum - $firstChar] = $font['C'][$charName];
2777
+ if ($font['isUnicode']) {
2778
+ $cid_widths[$charName] = $font['C'][$charName];
2779
+ }
2780
+ }
2781
+ }
2782
+ }
2783
+
2784
+ if ($font['isUnicode']) {
2785
+ $font['CIDWidths'] = $cid_widths;
2786
+ }
2787
+
2788
+ $this->addMessage('selectFont: FirstChar = ' . $firstChar);
2789
+ $this->addMessage('selectFont: LastChar = ' . $lastChar);
2790
+
2791
+ $widthid = -1;
2792
+
2793
+ if (!$font['isUnicode']) {
2794
+ // With Unicode, widths array isn't used
2795
+
2796
+ $this->numObj++;
2797
+ $this->o_contents($this->numObj, 'new', 'raw');
2798
+ $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
2799
+ $widthid = $this->numObj;
2800
+ }
2801
+
2802
+ $missing_width = 500;
2803
+ $stemV = 70;
2804
+
2805
+ if (isset($font['MissingWidth'])) {
2806
+ $missing_width = $font['MissingWidth'];
2807
+ }
2808
+ if (isset($font['StdVW'])) {
2809
+ $stemV = $font['StdVW'];
2810
+ } else {
2811
+ if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
2812
+ $stemV = 120;
2813
+ }
2814
+ }
2815
+
2816
+ // load the pfb file, and put that into an object too.
2817
+ // note that pdf supports only binary format type 1 font files, though there is a
2818
+ // simple utility to convert them from pfa to pfb.
2819
+ // FIXME: should we move font subset creation to CPDF::output? See notes in issue #750.
2820
+ if (!$this->isUnicode || strtolower($fbtype) !== 'ttf' || empty($this->stringSubsets)) {
2821
+ $data = file_get_contents($fbfile);
2822
+ } else {
2823
+ $this->stringSubsets[$fontName][] = 32; // Force space if not in yet
2824
+
2825
+ $subset = $this->stringSubsets[$fontName];
2826
+ sort($subset);
2827
+
2828
+ // Load font
2829
+ $font_obj = Font::load($fbfile);
2830
+ $font_obj->parse();
2831
+
2832
+ // Define subset
2833
+ $font_obj->setSubset($subset);
2834
+ $font_obj->reduce();
2835
+
2836
+ // Write new font
2837
+ $tmp_name = $this->tmp . "/" . basename($fbfile) . ".tmp." . uniqid();
2838
+ $font_obj->open($tmp_name, BinaryStream::modeWrite);
2839
+ $font_obj->encode(array("OS/2"));
2840
+ $font_obj->close();
2841
+
2842
+ // Parse the new font to get cid2gid and widths
2843
+ $font_obj = Font::load($tmp_name);
2844
+
2845
+ // Find Unicode char map table
2846
+ $subtable = null;
2847
+ foreach ($font_obj->getData("cmap", "subtables") as $_subtable) {
2848
+ if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
2849
+ $subtable = $_subtable;
2850
+ break;
2851
+ }
2852
+ }
2853
+
2854
+ if ($subtable) {
2855
+ $glyphIndexArray = $subtable["glyphIndexArray"];
2856
+ $hmtx = $font_obj->getData("hmtx");
2857
+
2858
+ unset($glyphIndexArray[0xFFFF]);
2859
+
2860
+ $cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00");
2861
+ $font['CIDWidths'] = array();
2862
+ foreach ($glyphIndexArray as $cid => $gid) {
2863
+ if ($cid >= 0 && $cid < 0xFFFF && $gid) {
2864
+ $cidtogid[$cid * 2] = chr($gid >> 8);
2865
+ $cidtogid[$cid * 2 + 1] = chr($gid & 0xFF);
2866
+ }
2867
+
2868
+ $width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]);
2869
+ $font['CIDWidths'][$cid] = $width;
2870
+ }
2871
+
2872
+ $font['CIDtoGID'] = base64_encode(gzcompress($cidtogid));
2873
+ $font['CIDtoGID_Compressed'] = true;
2874
+
2875
+ $data = file_get_contents($tmp_name);
2876
+ } else {
2877
+ $data = file_get_contents($fbfile);
2878
+ }
2879
+
2880
+ $font_obj->close();
2881
+ unlink($tmp_name);
2882
+ }
2883
+
2884
+ // create the font descriptor
2885
+ $this->numObj++;
2886
+ $fontDescriptorId = $this->numObj;
2887
+
2888
+ $this->numObj++;
2889
+ $pfbid = $this->numObj;
2890
+
2891
+ // determine flags (more than a little flakey, hopefully will not matter much)
2892
+ $flags = 0;
2893
+
2894
+ if ($font['ItalicAngle'] != 0) {
2895
+ $flags += pow(2, 6);
2896
+ }
2897
+
2898
+ if ($font['IsFixedPitch'] === 'true') {
2899
+ $flags += 1;
2900
+ }
2901
+
2902
+ $flags += pow(2, 5); // assume non-sybolic
2903
+ $list = array(
2904
+ 'Ascent' => 'Ascender',
2905
+ 'CapHeight' => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
2906
+ 'MissingWidth' => 'MissingWidth',
2907
+ 'Descent' => 'Descender',
2908
+ 'FontBBox' => 'FontBBox',
2909
+ 'ItalicAngle' => 'ItalicAngle'
2910
+ );
2911
+ $fdopt = array(
2912
+ 'Flags' => $flags,
2913
+ 'FontName' => $adobeFontName,
2914
+ 'StemV' => $stemV
2915
+ );
2916
+
2917
+ foreach ($list as $k => $v) {
2918
+ if (isset($font[$v])) {
2919
+ $fdopt[$k] = $font[$v];
2920
+ }
2921
+ }
2922
+
2923
+ if (strtolower($fbtype) === 'pfb') {
2924
+ $fdopt['FontFile'] = $pfbid;
2925
+ } elseif (strtolower($fbtype) === 'ttf') {
2926
+ $fdopt['FontFile2'] = $pfbid;
2927
+ }
2928
+
2929
+ $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
2930
+
2931
+ // embed the font program
2932
+ $this->o_contents($this->numObj, 'new');
2933
+ $this->objects[$pfbid]['c'] .= $data;
2934
+
2935
+ // determine the cruicial lengths within this file
2936
+ if (strtolower($fbtype) === 'pfb') {
2937
+ $l1 = strpos($data, 'eexec') + 6;
2938
+ $l2 = strpos($data, '00000000') - $l1;
2939
+ $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
2940
+ $this->o_contents(
2941
+ $this->numObj,
2942
+ 'add',
2943
+ array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3)
2944
+ );
2945
+ } elseif (strtolower($fbtype) == 'ttf') {
2946
+ $l1 = mb_strlen($data, '8bit');
2947
+ $this->o_contents($this->numObj, 'add', array('Length1' => $l1));
2948
+ }
2949
+
2950
+ // tell the font object about all this new stuff
2951
+ $tmp = array(
2952
+ 'BaseFont' => $adobeFontName,
2953
+ 'MissingWidth' => $missing_width,
2954
+ 'Widths' => $widthid,
2955
+ 'FirstChar' => $firstChar,
2956
+ 'LastChar' => $lastChar,
2957
+ 'FontDescriptor' => $fontDescriptorId
2958
+ );
2959
+
2960
+ if (strtolower($fbtype) === 'ttf') {
2961
+ $tmp['SubType'] = 'TrueType';
2962
+ }
2963
+
2964
+ $this->addMessage("adding extra info to font.($fontObj)");
2965
+
2966
+ foreach ($tmp as $fk => $fv) {
2967
+ $this->addMessage("$fk : $fv");
2968
+ }
2969
+
2970
+ $this->o_font($fontObj, 'add', $tmp);
2971
+ } else {
2972
+ $this->addMessage(
2973
+ 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
2974
+ );
2975
+ }
2976
+
2977
+ // also set the differences here, note that this means that these will take effect only the
2978
+ //first time that a font is selected, else they are ignored
2979
+ if (isset($options['differences'])) {
2980
+ $font['differences'] = $options['differences'];
2981
+ }
2982
+ }
2983
+ }
2984
+
2985
+ if ($set && isset($this->fonts[$fontName])) {
2986
+ // so if for some reason the font was not set in the last one then it will not be selected
2987
+ $this->currentBaseFont = $fontName;
2988
+
2989
+ // the next lines mean that if a new font is selected, then the current text state will be
2990
+ // applied to it as well.
2991
+ $this->currentFont = $this->currentBaseFont;
2992
+ $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
2993
+
2994
+ //$this->setCurrentFont();
2995
+ }
2996
+
2997
+ return $this->currentFontNum;
2998
+ //return $this->numObj;
2999
+ }
3000
+
3001
+ /**
3002
+ * sets up the current font, based on the font families, and the current text state
3003
+ * note that this system is quite flexible, a bold-italic font can be completely different to a
3004
+ * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3005
+ * This function is to be called whenever the currentTextState is changed, it will update
3006
+ * the currentFont setting to whatever the appropriate family one is.
3007
+ * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3008
+ * This function will change the currentFont to whatever it should be, but will not change the
3009
+ * currentBaseFont.
3010
+ */
3011
+ private function setCurrentFont()
3012
+ {
3013
+ // if (strlen($this->currentBaseFont) == 0){
3014
+ // // then assume an initial font
3015
+ // $this->selectFont($this->defaultFont);
3016
+ // }
3017
+ // $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3018
+ // if (strlen($this->currentTextState)
3019
+ // && isset($this->fontFamilies[$cf])
3020
+ // && isset($this->fontFamilies[$cf][$this->currentTextState])){
3021
+ // // then we are in some state or another
3022
+ // // and this font has a family, and the current setting exists within it
3023
+ // // select the font, then return it
3024
+ // $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3025
+ // $this->selectFont($nf,'',0);
3026
+ // $this->currentFont = $nf;
3027
+ // $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3028
+ // } else {
3029
+ // // the this font must not have the right family member for the current state
3030
+ // // simply assume the base font
3031
+ $this->currentFont = $this->currentBaseFont;
3032
+ $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3033
+ // }
3034
+ }
3035
+
3036
+ /**
3037
+ * function for the user to find out what the ID is of the first page that was created during
3038
+ * startup - useful if they wish to add something to it later.
3039
+ *
3040
+ * @return int
3041
+ */
3042
+ function getFirstPageId()
3043
+ {
3044
+ return $this->firstPageId;
3045
+ }
3046
+
3047
+ /**
3048
+ * add content to the currently active object
3049
+ *
3050
+ * @param $content
3051
+ */
3052
+ private function addContent($content)
3053
+ {
3054
+ $this->objects[$this->currentContents]['c'] .= $content;
3055
+ }
3056
+
3057
+ /**
3058
+ * sets the color for fill operations
3059
+ *
3060
+ * @param $color
3061
+ * @param bool $force
3062
+ */
3063
+ function setColor($color, $force = false)
3064
+ {
3065
+ $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null);
3066
+
3067
+ if (!$force && $this->currentColor == $new_color) {
3068
+ return;
3069
+ }
3070
+
3071
+ if (isset($new_color[3])) {
3072
+ $this->currentColor = $new_color;
3073
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3074
+ } else {
3075
+ if (isset($new_color[2])) {
3076
+ $this->currentColor = $new_color;
3077
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3078
+ }
3079
+ }
3080
+ }
3081
+
3082
+ /**
3083
+ * sets the color for fill operations
3084
+ *
3085
+ * @param $fillRule
3086
+ */
3087
+ function setFillRule($fillRule)
3088
+ {
3089
+ if (!in_array($fillRule, array("nonzero", "evenodd"))) {
3090
+ return;
3091
+ }
3092
+
3093
+ $this->fillRule = $fillRule;
3094
+ }
3095
+
3096
+ /**
3097
+ * sets the color for stroke operations
3098
+ *
3099
+ * @param $color
3100
+ * @param bool $force
3101
+ */
3102
+ function setStrokeColor($color, $force = false)
3103
+ {
3104
+ $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null);
3105
+
3106
+ if (!$force && $this->currentStrokeColor == $new_color) {
3107
+ return;
3108
+ }
3109
+
3110
+ if (isset($new_color[3])) {
3111
+ $this->currentStrokeColor = $new_color;
3112
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3113
+ } else {
3114
+ if (isset($new_color[2])) {
3115
+ $this->currentStrokeColor = $new_color;
3116
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3117
+ }
3118
+ }
3119
+ }
3120
+
3121
+ /**
3122
+ * Set the graphics state for compositions
3123
+ *
3124
+ * @param $parameters
3125
+ */
3126
+ function setGraphicsState($parameters)
3127
+ {
3128
+ // Create a new graphics state object if necessary
3129
+ if (($gstate = array_search($parameters, $this->gstates)) === false) {
3130
+ $this->numObj++;
3131
+ $this->o_extGState($this->numObj, 'new', $parameters);
3132
+ $gstate = $this->numStates;
3133
+ $this->gstates[$gstate] = $parameters;
3134
+ }
3135
+ $this->addContent("\n/GS$gstate gs");
3136
+ }
3137
+
3138
+ /**
3139
+ * Set current blend mode & opacity for lines.
3140
+ *
3141
+ * Valid blend modes are:
3142
+ *
3143
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3144
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3145
+ * Exclusion
3146
+ *
3147
+ * @param string $mode the blend mode to use
3148
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
3149
+ */
3150
+ function setLineTransparency($mode, $opacity)
3151
+ {
3152
+ static $blend_modes = array(
3153
+ "Normal",
3154
+ "Multiply",
3155
+ "Screen",
3156
+ "Overlay",
3157
+ "Darken",
3158
+ "Lighten",
3159
+ "ColorDogde",
3160
+ "ColorBurn",
3161
+ "HardLight",
3162
+ "SoftLight",
3163
+ "Difference",
3164
+ "Exclusion"
3165
+ );
3166
+
3167
+ if (!in_array($mode, $blend_modes)) {
3168
+ $mode = "Normal";
3169
+ }
3170
+
3171
+ // Only create a new graphics state if required
3172
+ if ($mode === $this->currentLineTransparency["mode"] &&
3173
+ $opacity == $this->currentLineTransparency["opacity"]
3174
+ ) {
3175
+ return;
3176
+ }
3177
+
3178
+ $this->currentLineTransparency["mode"] = $mode;
3179
+ $this->currentLineTransparency["opacity"] = $opacity;
3180
+
3181
+ $options = array(
3182
+ "BM" => "/$mode",
3183
+ "CA" => (float)$opacity
3184
+ );
3185
+
3186
+ $this->setGraphicsState($options);
3187
+ }
3188
+
3189
+ /**
3190
+ * Set current blend mode & opacity for filled objects.
3191
+ *
3192
+ * Valid blend modes are:
3193
+ *
3194
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3195
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3196
+ * Exclusion
3197
+ *
3198
+ * @param string $mode the blend mode to use
3199
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
3200
+ */
3201
+ function setFillTransparency($mode, $opacity)
3202
+ {
3203
+ static $blend_modes = array(
3204
+ "Normal",
3205
+ "Multiply",
3206
+ "Screen",
3207
+ "Overlay",
3208
+ "Darken",
3209
+ "Lighten",
3210
+ "ColorDogde",
3211
+ "ColorBurn",
3212
+ "HardLight",
3213
+ "SoftLight",
3214
+ "Difference",
3215
+ "Exclusion"
3216
+ );
3217
+
3218
+ if (!in_array($mode, $blend_modes)) {
3219
+ $mode = "Normal";
3220
+ }
3221
+
3222
+ if ($mode === $this->currentFillTransparency["mode"] &&
3223
+ $opacity == $this->currentFillTransparency["opacity"]
3224
+ ) {
3225
+ return;
3226
+ }
3227
+
3228
+ $this->currentFillTransparency["mode"] = $mode;
3229
+ $this->currentFillTransparency["opacity"] = $opacity;
3230
+
3231
+ $options = array(
3232
+ "BM" => "/$mode",
3233
+ "ca" => (float)$opacity,
3234
+ );
3235
+
3236
+ $this->setGraphicsState($options);
3237
+ }
3238
+
3239
+ /**
3240
+ * draw a line from one set of coordinates to another
3241
+ *
3242
+ * @param $x1
3243
+ * @param $y1
3244
+ * @param $x2
3245
+ * @param $y2
3246
+ * @param bool $stroke
3247
+ */
3248
+ function line($x1, $y1, $x2, $y2, $stroke = true)
3249
+ {
3250
+ $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3251
+
3252
+ if ($stroke) {
3253
+ $this->addContent(' S');
3254
+ }
3255
+ }
3256
+
3257
+ /**
3258
+ * draw a bezier curve based on 4 control points
3259
+ *
3260
+ * @param $x0
3261
+ * @param $y0
3262
+ * @param $x1
3263
+ * @param $y1
3264
+ * @param $x2
3265
+ * @param $y2
3266
+ * @param $x3
3267
+ * @param $y3
3268
+ */
3269
+ function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3270
+ {
3271
+ // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3272
+ // as the control points for the curve.
3273
+ $this->addContent(
3274
+ sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3275
+ );
3276
+ }
3277
+
3278
+ /**
3279
+ * draw a part of an ellipse
3280
+ *
3281
+ * @param $x0
3282
+ * @param $y0
3283
+ * @param $astart
3284
+ * @param $afinish
3285
+ * @param $r1
3286
+ * @param int $r2
3287
+ * @param int $angle
3288
+ * @param int $nSeg
3289
+ */
3290
+ function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3291
+ {
3292
+ $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3293
+ }
3294
+
3295
+ /**
3296
+ * draw a filled ellipse
3297
+ *
3298
+ * @param $x0
3299
+ * @param $y0
3300
+ * @param $r1
3301
+ * @param int $r2
3302
+ * @param int $angle
3303
+ * @param int $nSeg
3304
+ * @param int $astart
3305
+ * @param int $afinish
3306
+ */
3307
+ function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3308
+ {
3309
+ $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3310
+ }
3311
+
3312
+ /**
3313
+ * @param $x
3314
+ * @param $y
3315
+ */
3316
+ function lineTo($x, $y)
3317
+ {
3318
+ $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3319
+ }
3320
+
3321
+ /**
3322
+ * @param $x
3323
+ * @param $y
3324
+ */
3325
+ function moveTo($x, $y)
3326
+ {
3327
+ $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3328
+ }
3329
+
3330
+ /**
3331
+ * draw a bezier curve based on 4 control points
3332
+ *
3333
+ * @param $x1
3334
+ * @param $y1
3335
+ * @param $x2
3336
+ * @param $y2
3337
+ * @param $x3
3338
+ * @param $y3
3339
+ */
3340
+ function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3341
+ {
3342
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3343
+ }
3344
+
3345
+ /**
3346
+ * draw a bezier curve based on 4 control points
3347
+ */ function quadTo($cpx, $cpy, $x, $y)
3348
+ {
3349
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3350
+ }
3351
+
3352
+ function closePath()
3353
+ {
3354
+ $this->addContent(' h');
3355
+ }
3356
+
3357
+ function endPath()
3358
+ {
3359
+ $this->addContent(' n');
3360
+ }
3361
+
3362
+ /**
3363
+ * draw an ellipse
3364
+ * note that the part and filled ellipse are just special cases of this function
3365
+ *
3366
+ * draws an ellipse in the current line style
3367
+ * centered at $x0,$y0, radii $r1,$r2
3368
+ * if $r2 is not set, then a circle is drawn
3369
+ * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3370
+ * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3371
+ * pretty crappy shape at 2, as we are approximating with bezier curves.
3372
+ *
3373
+ * @param $x0
3374
+ * @param $y0
3375
+ * @param $r1
3376
+ * @param int $r2
3377
+ * @param int $angle
3378
+ * @param int $nSeg
3379
+ * @param int $astart
3380
+ * @param int $afinish
3381
+ * @param bool $close
3382
+ * @param bool $fill
3383
+ * @param bool $stroke
3384
+ * @param bool $incomplete
3385
+ */
3386
+ function ellipse(
3387
+ $x0,
3388
+ $y0,
3389
+ $r1,
3390
+ $r2 = 0,
3391
+ $angle = 0,
3392
+ $nSeg = 8,
3393
+ $astart = 0,
3394
+ $afinish = 360,
3395
+ $close = true,
3396
+ $fill = false,
3397
+ $stroke = true,
3398
+ $incomplete = false
3399
+ ) {
3400
+ if ($r1 == 0) {
3401
+ return;
3402
+ }
3403
+
3404
+ if ($r2 == 0) {
3405
+ $r2 = $r1;
3406
+ }
3407
+
3408
+ if ($nSeg < 2) {
3409
+ $nSeg = 2;
3410
+ }
3411
+
3412
+ $astart = deg2rad((float)$astart);
3413
+ $afinish = deg2rad((float)$afinish);
3414
+ $totalAngle = $afinish - $astart;
3415
+
3416
+ $dt = $totalAngle / $nSeg;
3417
+ $dtm = $dt / 3;
3418
+
3419
+ if ($angle != 0) {
3420
+ $a = -1 * deg2rad((float)$angle);
3421
+
3422
+ $this->addContent(
3423
+ sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
3424
+ );
3425
+
3426
+ $x0 = 0;
3427
+ $y0 = 0;
3428
+ }
3429
+
3430
+ $t1 = $astart;
3431
+ $a0 = $x0 + $r1 * cos($t1);
3432
+ $b0 = $y0 + $r2 * sin($t1);
3433
+ $c0 = -$r1 * sin($t1);
3434
+ $d0 = $r2 * cos($t1);
3435
+
3436
+ if (!$incomplete) {
3437
+ $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
3438
+ }
3439
+
3440
+ for ($i = 1; $i <= $nSeg; $i++) {
3441
+ // draw this bit of the total curve
3442
+ $t1 = $i * $dt + $astart;
3443
+ $a1 = $x0 + $r1 * cos($t1);
3444
+ $b1 = $y0 + $r2 * sin($t1);
3445
+ $c1 = -$r1 * sin($t1);
3446
+ $d1 = $r2 * cos($t1);
3447
+
3448
+ $this->addContent(
3449
+ sprintf(
3450
+ "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
3451
+ ($a0 + $c0 * $dtm),
3452
+ ($b0 + $d0 * $dtm),
3453
+ ($a1 - $c1 * $dtm),
3454
+ ($b1 - $d1 * $dtm),
3455
+ $a1,
3456
+ $b1
3457
+ )
3458
+ );
3459
+
3460
+ $a0 = $a1;
3461
+ $b0 = $b1;
3462
+ $c0 = $c1;
3463
+ $d0 = $d1;
3464
+ }
3465
+
3466
+ if (!$incomplete) {
3467
+ if ($fill) {
3468
+ $this->addContent(' f');
3469
+ }
3470
+
3471
+ if ($stroke) {
3472
+ if ($close) {
3473
+ $this->addContent(' s'); // small 's' signifies closing the path as well
3474
+ } else {
3475
+ $this->addContent(' S');
3476
+ }
3477
+ }
3478
+ }
3479
+
3480
+ if ($angle != 0) {
3481
+ $this->addContent(' Q');
3482
+ }
3483
+ }
3484
+
3485
+ /**
3486
+ * this sets the line drawing style.
3487
+ * width, is the thickness of the line in user units
3488
+ * cap is the type of cap to put on the line, values can be 'butt','round','square'
3489
+ * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
3490
+ * end of the line.
3491
+ * join can be 'miter', 'round', 'bevel'
3492
+ * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
3493
+ * on and off dashes.
3494
+ * (2) represents 2 on, 2 off, 2 on , 2 off ...
3495
+ * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
3496
+ * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
3497
+ *
3498
+ * @param int $width
3499
+ * @param string $cap
3500
+ * @param string $join
3501
+ * @param string $dash
3502
+ * @param int $phase
3503
+ */
3504
+ function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
3505
+ {
3506
+ // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
3507
+ $string = '';
3508
+
3509
+ if ($width > 0) {
3510
+ $string .= "$width w";
3511
+ }
3512
+
3513
+ $ca = array('butt' => 0, 'round' => 1, 'square' => 2);
3514
+
3515
+ if (isset($ca[$cap])) {
3516
+ $string .= " $ca[$cap] J";
3517
+ }
3518
+
3519
+ $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
3520
+
3521
+ if (isset($ja[$join])) {
3522
+ $string .= " $ja[$join] j";
3523
+ }
3524
+
3525
+ if (is_array($dash)) {
3526
+ $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
3527
+ }
3528
+
3529
+ $this->currentLineStyle = $string;
3530
+ $this->addContent("\n$string");
3531
+ }
3532
+
3533
+ /**
3534
+ * draw a polygon, the syntax for this is similar to the GD polygon command
3535
+ *
3536
+ * @param $p
3537
+ * @param $np
3538
+ * @param bool $f
3539
+ */
3540
+ function polygon($p, $np, $f = false)
3541
+ {
3542
+ $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
3543
+
3544
+ for ($i = 2; $i < $np * 2; $i = $i + 2) {
3545
+ $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
3546
+ }
3547
+
3548
+ if ($f) {
3549
+ $this->addContent(' f');
3550
+ } else {
3551
+ $this->addContent(' S');
3552
+ }
3553
+ }
3554
+
3555
+ /**
3556
+ * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3557
+ * the coordinates of the upper-right corner
3558
+ *
3559
+ * @param $x1
3560
+ * @param $y1
3561
+ * @param $width
3562
+ * @param $height
3563
+ */
3564
+ function filledRectangle($x1, $y1, $width, $height)
3565
+ {
3566
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
3567
+ }
3568
+
3569
+ /**
3570
+ * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3571
+ * the coordinates of the upper-right corner
3572
+ *
3573
+ * @param $x1
3574
+ * @param $y1
3575
+ * @param $width
3576
+ * @param $height
3577
+ */
3578
+ function rectangle($x1, $y1, $width, $height)
3579
+ {
3580
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
3581
+ }
3582
+
3583
+ /**
3584
+ * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3585
+ * the coordinates of the upper-right corner
3586
+ *
3587
+ * @param $x1
3588
+ * @param $y1
3589
+ * @param $width
3590
+ * @param $height
3591
+ */
3592
+ function rect($x1, $y1, $width, $height)
3593
+ {
3594
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
3595
+ }
3596
+
3597
+ function stroke()
3598
+ {
3599
+ $this->addContent("\nS");
3600
+ }
3601
+
3602
+ function fill()
3603
+ {
3604
+ $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
3605
+ }
3606
+
3607
+ function fillStroke()
3608
+ {
3609
+ $this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : ""));
3610
+ }
3611
+
3612
+ /**
3613
+ * save the current graphic state
3614
+ */
3615
+ function save()
3616
+ {
3617
+ // we must reset the color cache or it will keep bad colors after clipping
3618
+ $this->currentColor = null;
3619
+ $this->currentStrokeColor = null;
3620
+ $this->addContent("\nq");
3621
+ }
3622
+
3623
+ /**
3624
+ * restore the last graphic state
3625
+ */
3626
+ function restore()
3627
+ {
3628
+ // we must reset the color cache or it will keep bad colors after clipping
3629
+ $this->currentColor = null;
3630
+ $this->currentStrokeColor = null;
3631
+ $this->addContent("\nQ");
3632
+ }
3633
+
3634
+ /**
3635
+ * draw a clipping rectangle, all the elements added after this will be clipped
3636
+ *
3637
+ * @param $x1
3638
+ * @param $y1
3639
+ * @param $width
3640
+ * @param $height
3641
+ */
3642
+ function clippingRectangle($x1, $y1, $width, $height)
3643
+ {
3644
+ $this->save();
3645
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
3646
+ }
3647
+
3648
+ /**
3649
+ * draw a clipping rounded rectangle, all the elements added after this will be clipped
3650
+ *
3651
+ * @param $x1
3652
+ * @param $y1
3653
+ * @param $w
3654
+ * @param $h
3655
+ * @param $rTL
3656
+ * @param $rTR
3657
+ * @param $rBR
3658
+ * @param $rBL
3659
+ */
3660
+ function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
3661
+ {
3662
+ $this->save();
3663
+
3664
+ // start: top edge, left end
3665
+ $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
3666
+
3667
+ // line: bottom edge, left end
3668
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
3669
+
3670
+ // curve: bottom-left corner
3671
+ $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
3672
+
3673
+ // line: right edge, bottom end
3674
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
3675
+
3676
+ // curve: bottom-right corner
3677
+ $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
3678
+
3679
+ // line: right edge, top end
3680
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
3681
+
3682
+ // curve: bottom-right corner
3683
+ $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
3684
+
3685
+ // line: bottom edge, right end
3686
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
3687
+
3688
+ // curve: top-right corner
3689
+ $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
3690
+
3691
+ // line: top edge, left end
3692
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
3693
+
3694
+ // Close & clip
3695
+ $this->addContent(" W n");
3696
+ }
3697
+
3698
+ /**
3699
+ * ends the last clipping shape
3700
+ */
3701
+ function clippingEnd()
3702
+ {
3703
+ $this->restore();
3704
+ }
3705
+
3706
+ /**
3707
+ * scale
3708
+ *
3709
+ * @param float $s_x scaling factor for width as percent
3710
+ * @param float $s_y scaling factor for height as percent
3711
+ * @param float $x Origin abscissa
3712
+ * @param float $y Origin ordinate
3713
+ */
3714
+ function scale($s_x, $s_y, $x, $y)
3715
+ {
3716
+ $y = $this->currentPageSize["height"] - $y;
3717
+
3718
+ $tm = array(
3719
+ $s_x,
3720
+ 0,
3721
+ 0,
3722
+ $s_y,
3723
+ $x * (1 - $s_x),
3724
+ $y * (1 - $s_y)
3725
+ );
3726
+
3727
+ $this->transform($tm);
3728
+ }
3729
+
3730
+ /**
3731
+ * translate
3732
+ *
3733
+ * @param float $t_x movement to the right
3734
+ * @param float $t_y movement to the bottom
3735
+ */
3736
+ function translate($t_x, $t_y)
3737
+ {
3738
+ $tm = array(
3739
+ 1,
3740
+ 0,
3741
+ 0,
3742
+ 1,
3743
+ $t_x,
3744
+ -$t_y
3745
+ );
3746
+
3747
+ $this->transform($tm);
3748
+ }
3749
+
3750
+ /**
3751
+ * rotate
3752
+ *
3753
+ * @param float $angle angle in degrees for counter-clockwise rotation
3754
+ * @param float $x Origin abscissa
3755
+ * @param float $y Origin ordinate
3756
+ */
3757
+ function rotate($angle, $x, $y)
3758
+ {
3759
+ $y = $this->currentPageSize["height"] - $y;
3760
+
3761
+ $a = deg2rad($angle);
3762
+ $cos_a = cos($a);
3763
+ $sin_a = sin($a);
3764
+
3765
+ $tm = array(
3766
+ $cos_a,
3767
+ -$sin_a,
3768
+ $sin_a,
3769
+ $cos_a,
3770
+ $x - $sin_a * $y - $cos_a * $x,
3771
+ $y - $cos_a * $y + $sin_a * $x,
3772
+ );
3773
+
3774
+ $this->transform($tm);
3775
+ }
3776
+
3777
+ /**
3778
+ * skew
3779
+ *
3780
+ * @param float $angle_x
3781
+ * @param float $angle_y
3782
+ * @param float $x Origin abscissa
3783
+ * @param float $y Origin ordinate
3784
+ */
3785
+ function skew($angle_x, $angle_y, $x, $y)
3786
+ {
3787
+ $y = $this->currentPageSize["height"] - $y;
3788
+
3789
+ $tan_x = tan(deg2rad($angle_x));
3790
+ $tan_y = tan(deg2rad($angle_y));
3791
+
3792
+ $tm = array(
3793
+ 1,
3794
+ -$tan_y,
3795
+ -$tan_x,
3796
+ 1,
3797
+ $tan_x * $y,
3798
+ $tan_y * $x,
3799
+ );
3800
+
3801
+ $this->transform($tm);
3802
+ }
3803
+
3804
+ /**
3805
+ * apply graphic transformations
3806
+ *
3807
+ * @param array $tm transformation matrix
3808
+ */
3809
+ function transform($tm)
3810
+ {
3811
+ $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
3812
+ }
3813
+
3814
+ /**
3815
+ * add a new page to the document
3816
+ * this also makes the new page the current active object
3817
+ *
3818
+ * @param int $insert
3819
+ * @param int $id
3820
+ * @param string $pos
3821
+ * @return int
3822
+ */
3823
+ function newPage($insert = 0, $id = 0, $pos = 'after')
3824
+ {
3825
+ // if there is a state saved, then go up the stack closing them
3826
+ // then on the new page, re-open them with the right setings
3827
+
3828
+ if ($this->nStateStack) {
3829
+ for ($i = $this->nStateStack; $i >= 1; $i--) {
3830
+ $this->restoreState($i);
3831
+ }
3832
+ }
3833
+
3834
+ $this->numObj++;
3835
+
3836
+ if ($insert) {
3837
+ // the id from the ezPdf class is the id of the contents of the page, not the page object itself
3838
+ // query that object to find the parent
3839
+ $rid = $this->objects[$id]['onPage'];
3840
+ $opt = array('rid' => $rid, 'pos' => $pos);
3841
+ $this->o_page($this->numObj, 'new', $opt);
3842
+ } else {
3843
+ $this->o_page($this->numObj, 'new');
3844
+ }
3845
+
3846
+ // if there is a stack saved, then put that onto the page
3847
+ if ($this->nStateStack) {
3848
+ for ($i = 1; $i <= $this->nStateStack; $i++) {
3849
+ $this->saveState($i);
3850
+ }
3851
+ }
3852
+
3853
+ // and if there has been a stroke or fill color set, then transfer them
3854
+ if (isset($this->currentColor)) {
3855
+ $this->setColor($this->currentColor, true);
3856
+ }
3857
+
3858
+ if (isset($this->currentStrokeColor)) {
3859
+ $this->setStrokeColor($this->currentStrokeColor, true);
3860
+ }
3861
+
3862
+ // if there is a line style set, then put this in too
3863
+ if (mb_strlen($this->currentLineStyle, '8bit')) {
3864
+ $this->addContent("\n$this->currentLineStyle");
3865
+ }
3866
+
3867
+ // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
3868
+ return $this->currentContents;
3869
+ }
3870
+
3871
+ /**
3872
+ * Streams the PDF to the client.
3873
+ *
3874
+ * @param string $filename The filename to present to the client.
3875
+ * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
3876
+ */
3877
+ function stream($filename = "document.pdf", $options = array())
3878
+ {
3879
+ if (headers_sent()) {
3880
+ die("Unable to stream pdf: headers already sent");
3881
+ }
3882
+
3883
+ if (!isset($options["compress"])) $options["compress"] = true;
3884
+ if (!isset($options["Attachment"])) $options["Attachment"] = true;
3885
+
3886
+ $debug = !$options['compress'];
3887
+ $tmp = ltrim($this->output($debug));
3888
+
3889
+ header("Cache-Control: private");
3890
+ header("Content-Type: application/pdf");
3891
+ header("Content-Length: " . mb_strlen($tmp, "8bit"));
3892
+
3893
+ $filename = str_replace(array("\n", "'"), "", basename($filename, ".pdf")) . ".pdf";
3894
+ $attachment = $options["Attachment"] ? "attachment" : "inline";
3895
+
3896
+ $encoding = mb_detect_encoding($filename);
3897
+ $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
3898
+ $fallbackfilename = str_replace("\"", "", $fallbackfilename);
3899
+ $encodedfilename = rawurlencode($filename);
3900
+
3901
+ $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
3902
+ if ($fallbackfilename !== $filename) {
3903
+ $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
3904
+ }
3905
+ header($contentDisposition);
3906
+
3907
+ echo $tmp;
3908
+ flush();
3909
+ }
3910
+
3911
+ /**
3912
+ * return the height in units of the current font in the given size
3913
+ *
3914
+ * @param $size
3915
+ * @return float|int
3916
+ */
3917
+ function getFontHeight($size)
3918
+ {
3919
+ if (!$this->numFonts) {
3920
+ $this->selectFont($this->defaultFont);
3921
+ }
3922
+
3923
+ $font = $this->fonts[$this->currentFont];
3924
+
3925
+ // for the current font, and the given size, what is the height of the font in user units
3926
+ if (isset($font['Ascender']) && isset($font['Descender'])) {
3927
+ $h = $font['Ascender'] - $font['Descender'];
3928
+ } else {
3929
+ $h = $font['FontBBox'][3] - $font['FontBBox'][1];
3930
+ }
3931
+
3932
+ // have to adjust by a font offset for Windows fonts. unfortunately it looks like
3933
+ // the bounding box calculations are wrong and I don't know why.
3934
+ if (isset($font['FontHeightOffset'])) {
3935
+ // For CourierNew from Windows this needs to be -646 to match the
3936
+ // Adobe native Courier font.
3937
+ //
3938
+ // For FreeMono from GNU this needs to be -337 to match the
3939
+ // Courier font.
3940
+ //
3941
+ // Both have been added manually to the .afm and .ufm files.
3942
+ $h += (int)$font['FontHeightOffset'];
3943
+ }
3944
+
3945
+ return $size * $h / 1000;
3946
+ }
3947
+
3948
+ /**
3949
+ * @param $size
3950
+ * @return float|int
3951
+ */
3952
+ function getFontXHeight($size)
3953
+ {
3954
+ if (!$this->numFonts) {
3955
+ $this->selectFont($this->defaultFont);
3956
+ }
3957
+
3958
+ $font = $this->fonts[$this->currentFont];
3959
+
3960
+ // for the current font, and the given size, what is the height of the font in user units
3961
+ if (isset($font['XHeight'])) {
3962
+ $xh = $font['Ascender'] - $font['Descender'];
3963
+ } else {
3964
+ $xh = $this->getFontHeight($size) / 2;
3965
+ }
3966
+
3967
+ return $size * $xh / 1000;
3968
+ }
3969
+
3970
+ /**
3971
+ * return the font descender, this will normally return a negative number
3972
+ * if you add this number to the baseline, you get the level of the bottom of the font
3973
+ * it is in the pdf user units
3974
+ *
3975
+ * @param $size
3976
+ * @return float|int
3977
+ */
3978
+ function getFontDescender($size)
3979
+ {
3980
+ // note that this will most likely return a negative value
3981
+ if (!$this->numFonts) {
3982
+ $this->selectFont($this->defaultFont);
3983
+ }
3984
+
3985
+ //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
3986
+ $h = $this->fonts[$this->currentFont]['Descender'];
3987
+
3988
+ return $size * $h / 1000;
3989
+ }
3990
+
3991
+ /**
3992
+ * filter the text, this is applied to all text just before being inserted into the pdf document
3993
+ * it escapes the various things that need to be escaped, and so on
3994
+ *
3995
+ * @access private
3996
+ *
3997
+ * @param $text
3998
+ * @param bool $bom
3999
+ * @param bool $convert_encoding
4000
+ * @return string
4001
+ */
4002
+ function filterText($text, $bom = true, $convert_encoding = true)
4003
+ {
4004
+ if (!$this->numFonts) {
4005
+ $this->selectFont($this->defaultFont);
4006
+ }
4007
+
4008
+ if ($convert_encoding) {
4009
+ $cf = $this->currentFont;
4010
+ if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4011
+ $text = $this->utf8toUtf16BE($text, $bom);
4012
+ } else {
4013
+ //$text = html_entity_decode($text, ENT_QUOTES);
4014
+ $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4015
+ }
4016
+ } else if ($bom) {
4017
+ $text = $this->utf8toUtf16BE($text, $bom);
4018
+ }
4019
+
4020
+ // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4021
+ return strtr($text, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
4022
+ }
4023
+
4024
+ /**
4025
+ * return array containing codepoints (UTF-8 character values) for the
4026
+ * string passed in.
4027
+ *
4028
+ * based on the excellent TCPDF code by Nicola Asuni and the
4029
+ * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4030
+ *
4031
+ * @access private
4032
+ * @author Orion Richardson
4033
+ * @since January 5, 2008
4034
+ *
4035
+ * @param string $text UTF-8 string to process
4036
+ *
4037
+ * @return array UTF-8 codepoints array for the string
4038
+ */
4039
+ function utf8toCodePointsArray(&$text)
4040
+ {
4041
+ $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4042
+ $unicode = array(); // array containing unicode values
4043
+ $bytes = array(); // array containing single character byte sequences
4044
+ $numbytes = 1; // number of octets needed to represent the UTF-8 character
4045
+
4046
+ for ($i = 0; $i < $length; $i++) {
4047
+ $c = ord($text[$i]); // get one string character at time
4048
+ if (count($bytes) === 0) { // get starting octect
4049
+ if ($c <= 0x7F) {
4050
+ $unicode[] = $c; // use the character "as is" because is ASCII
4051
+ $numbytes = 1;
4052
+ } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4053
+ $bytes[] = ($c - 0xC0) << 0x06;
4054
+ $numbytes = 2;
4055
+ } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4056
+ $bytes[] = ($c - 0xE0) << 0x0C;
4057
+ $numbytes = 3;
4058
+ } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4059
+ $bytes[] = ($c - 0xF0) << 0x12;
4060
+ $numbytes = 4;
4061
+ } else {
4062
+ // use replacement character for other invalid sequences
4063
+ $unicode[] = 0xFFFD;
4064
+ $bytes = array();
4065
+ $numbytes = 1;
4066
+ }
4067
+ } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4068
+ $bytes[] = $c - 0x80;
4069
+ if (count($bytes) === $numbytes) {
4070
+ // compose UTF-8 bytes to a single unicode value
4071
+ $c = $bytes[0];
4072
+ for ($j = 1; $j < $numbytes; $j++) {
4073
+ $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4074
+ }
4075
+ if ((($c >= 0xD800) AND ($c <= 0xDFFF)) OR ($c >= 0x10FFFF)) {
4076
+ // The definition of UTF-8 prohibits encoding character numbers between
4077
+ // U+D800 and U+DFFF, which are reserved for use with the UTF-16
4078
+ // encoding form (as surrogate pairs) and do not directly represent
4079
+ // characters.
4080
+ $unicode[] = 0xFFFD; // use replacement character
4081
+ } else {
4082
+ $unicode[] = $c; // add char to array
4083
+ }
4084
+ // reset data for next char
4085
+ $bytes = array();
4086
+ $numbytes = 1;
4087
+ }
4088
+ } else {
4089
+ // use replacement character for other invalid sequences
4090
+ $unicode[] = 0xFFFD;
4091
+ $bytes = array();
4092
+ $numbytes = 1;
4093
+ }
4094
+ }
4095
+
4096
+ return $unicode;
4097
+ }
4098
+
4099
+ /**
4100
+ * convert UTF-8 to UTF-16 with an additional byte order marker
4101
+ * at the front if required.
4102
+ *
4103
+ * based on the excellent TCPDF code by Nicola Asuni and the
4104
+ * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4105
+ *
4106
+ * @access private
4107
+ * @author Orion Richardson
4108
+ * @since January 5, 2008
4109
+ *
4110
+ * @param string $text UTF-8 string to process
4111
+ * @param boolean $bom whether to add the byte order marker
4112
+ *
4113
+ * @return string UTF-16 result string
4114
+ */
4115
+ function utf8toUtf16BE(&$text, $bom = true)
4116
+ {
4117
+ $out = $bom ? "\xFE\xFF" : '';
4118
+
4119
+ $unicode = $this->utf8toCodePointsArray($text);
4120
+ foreach ($unicode as $c) {
4121
+ if ($c === 0xFFFD) {
4122
+ $out .= "\xFF\xFD"; // replacement character
4123
+ } elseif ($c < 0x10000) {
4124
+ $out .= chr($c >> 0x08) . chr($c & 0xFF);
4125
+ } else {
4126
+ $c -= 0x10000;
4127
+ $w1 = 0xD800 | ($c >> 0x10);
4128
+ $w2 = 0xDC00 | ($c & 0x3FF);
4129
+ $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4130
+ }
4131
+ }
4132
+
4133
+ return $out;
4134
+ }
4135
+
4136
+ /**
4137
+ * given a start position and information about how text is to be laid out, calculate where
4138
+ * on the page the text will end
4139
+ *
4140
+ * @param $x
4141
+ * @param $y
4142
+ * @param $angle
4143
+ * @param $size
4144
+ * @param $wa
4145
+ * @param $text
4146
+ * @return array
4147
+ */
4148
+ private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4149
+ {
4150
+ // given this information return an array containing x and y for the end position as elements 0 and 1
4151
+ $w = $this->getTextWidth($size, $text);
4152
+
4153
+ // need to adjust for the number of spaces in this text
4154
+ $words = explode(' ', $text);
4155
+ $nspaces = count($words) - 1;
4156
+ $w += $wa * $nspaces;
4157
+ $a = deg2rad((float)$angle);
4158
+
4159
+ return array(cos($a) * $w + $x, -sin($a) * $w + $y);
4160
+ }
4161
+
4162
+ /**
4163
+ * Callback method used by smallCaps
4164
+ *
4165
+ * @param array $matches
4166
+ *
4167
+ * @return string
4168
+ */
4169
+ function toUpper($matches)
4170
+ {
4171
+ return mb_strtoupper($matches[0]);
4172
+ }
4173
+
4174
+ function concatMatches($matches)
4175
+ {
4176
+ $str = "";
4177
+ foreach ($matches as $match) {
4178
+ $str .= $match[0];
4179
+ }
4180
+
4181
+ return $str;
4182
+ }
4183
+
4184
+ /**
4185
+ * register text for font subsetting
4186
+ *
4187
+ * @param $font
4188
+ * @param $text
4189
+ */
4190
+ function registerText($font, $text)
4191
+ {
4192
+ if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4193
+ return;
4194
+ }
4195
+
4196
+ if (!isset($this->stringSubsets[$font])) {
4197
+ $this->stringSubsets[$font] = array();
4198
+ }
4199
+
4200
+ $this->stringSubsets[$font] = array_unique(
4201
+ array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
4202
+ );
4203
+ }
4204
+
4205
+ /**
4206
+ * add text to the document, at a specified location, size and angle on the page
4207
+ *
4208
+ * @param $x
4209
+ * @param $y
4210
+ * @param $size
4211
+ * @param $text
4212
+ * @param int $angle
4213
+ * @param int $wordSpaceAdjust
4214
+ * @param int $charSpaceAdjust
4215
+ * @param bool $smallCaps
4216
+ */
4217
+ function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4218
+ {
4219
+ if (!$this->numFonts) {
4220
+ $this->selectFont($this->defaultFont);
4221
+ }
4222
+
4223
+ $text = str_replace(array("\r", "\n"), "", $text);
4224
+
4225
+ if ($smallCaps) {
4226
+ preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4227
+ $lower = $this->concatMatches($matches);
4228
+ d($lower);
4229
+
4230
+ preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4231
+ $other = $this->concatMatches($matches);
4232
+ d($other);
4233
+
4234
+ //$text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
4235
+ }
4236
+
4237
+ // if there are any open callbacks, then they should be called, to show the start of the line
4238
+ if ($this->nCallback > 0) {
4239
+ for ($i = $this->nCallback; $i > 0; $i--) {
4240
+ // call each function
4241
+ $info = array(
4242
+ 'x' => $x,
4243
+ 'y' => $y,
4244
+ 'angle' => $angle,
4245
+ 'status' => 'sol',
4246
+ 'p' => $this->callback[$i]['p'],
4247
+ 'nCallback' => $this->callback[$i]['nCallback'],
4248
+ 'height' => $this->callback[$i]['height'],
4249
+ 'descender' => $this->callback[$i]['descender']
4250
+ );
4251
+
4252
+ $func = $this->callback[$i]['f'];
4253
+ $this->$func($info);
4254
+ }
4255
+ }
4256
+
4257
+ if ($angle == 0) {
4258
+ $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
4259
+ } else {
4260
+ $a = deg2rad((float)$angle);
4261
+ $this->addContent(
4262
+ sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
4263
+ );
4264
+ }
4265
+
4266
+ if ($wordSpaceAdjust != 0) {
4267
+ $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
4268
+ }
4269
+
4270
+ if ($charSpaceAdjust != 0) {
4271
+ $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
4272
+ }
4273
+
4274
+ $len = mb_strlen($text);
4275
+ $start = 0;
4276
+
4277
+ if ($start < $len) {
4278
+ $part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start);
4279
+ $place_text = $this->filterText($part, false);
4280
+ // modify unicode text so that extra word spacing is manually implemented (bug #)
4281
+ $cf = $this->currentFont;
4282
+ if ($this->fonts[$cf]['isUnicode'] && $wordSpaceAdjust != 0) {
4283
+ $space_scale = 1000 / $size;
4284
+ $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
4285
+ }
4286
+ $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
4287
+ $this->addContent(" [($place_text)] TJ");
4288
+ }
4289
+
4290
+ if ($wordSpaceAdjust != 0) {
4291
+ $this->addContent(sprintf(" %.3F Tw", 0));
4292
+ }
4293
+
4294
+ if ($charSpaceAdjust != 0) {
4295
+ $this->addContent(sprintf(" %.3F Tc", 0));
4296
+ }
4297
+
4298
+ $this->addContent(' ET');
4299
+
4300
+ // if there are any open callbacks, then they should be called, to show the end of the line
4301
+ if ($this->nCallback > 0) {
4302
+ for ($i = $this->nCallback; $i > 0; $i--) {
4303
+ // call each function
4304
+ $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
4305
+ $info = array(
4306
+ 'x' => $tmp[0],
4307
+ 'y' => $tmp[1],
4308
+ 'angle' => $angle,
4309
+ 'status' => 'eol',
4310
+ 'p' => $this->callback[$i]['p'],
4311
+ 'nCallback' => $this->callback[$i]['nCallback'],
4312
+ 'height' => $this->callback[$i]['height'],
4313
+ 'descender' => $this->callback[$i]['descender']
4314
+ );
4315
+ $func = $this->callback[$i]['f'];
4316
+ $this->$func($info);
4317
+ }
4318
+ }
4319
+ }
4320
+
4321
+ /**
4322
+ * calculate how wide a given text string will be on a page, at a given size.
4323
+ * this can be called externally, but is also used by the other class functions
4324
+ *
4325
+ * @param $size
4326
+ * @param $text
4327
+ * @param int $word_spacing
4328
+ * @param int $char_spacing
4329
+ * @return float|int
4330
+ */
4331
+ function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0)
4332
+ {
4333
+ static $ord_cache = array();
4334
+
4335
+ // this function should not change any of the settings, though it will need to
4336
+ // track any directives which change during calculation, so copy them at the start
4337
+ // and put them back at the end.
4338
+ $store_currentTextState = $this->currentTextState;
4339
+
4340
+ if (!$this->numFonts) {
4341
+ $this->selectFont($this->defaultFont);
4342
+ }
4343
+
4344
+ $text = str_replace(array("\r", "\n"), "", $text);
4345
+
4346
+ // converts a number or a float to a string so it can get the width
4347
+ $text = "$text";
4348
+
4349
+ // hmm, this is where it all starts to get tricky - use the font information to
4350
+ // calculate the width of each character, add them up and convert to user units
4351
+ $w = 0;
4352
+ $cf = $this->currentFont;
4353
+ $current_font = $this->fonts[$cf];
4354
+ $space_scale = 1000 / ($size > 0 ? $size : 1);
4355
+ $n_spaces = 0;
4356
+
4357
+ if ($current_font['isUnicode']) {
4358
+ // for Unicode, use the code points array to calculate width rather
4359
+ // than just the string itself
4360
+ $unicode = $this->utf8toCodePointsArray($text);
4361
+
4362
+ foreach ($unicode as $char) {
4363
+ // check if we have to replace character
4364
+ if (isset($current_font['differences'][$char])) {
4365
+ $char = $current_font['differences'][$char];
4366
+ }
4367
+
4368
+ if (isset($current_font['C'][$char])) {
4369
+ $char_width = $current_font['C'][$char];
4370
+
4371
+ // add the character width
4372
+ $w += $char_width;
4373
+
4374
+ // add additional padding for space
4375
+ if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
4376
+ $w += $word_spacing * $space_scale;
4377
+ $n_spaces++;
4378
+ }
4379
+ }
4380
+ }
4381
+
4382
+ // add additional char spacing
4383
+ if ($char_spacing != 0) {
4384
+ $w += $char_spacing * $space_scale * (count($unicode) + $n_spaces);
4385
+ }
4386
+
4387
+ } else {
4388
+ // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
4389
+ if ($this->isUnicode) {
4390
+ $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
4391
+ }
4392
+
4393
+ $len = mb_strlen($text, 'Windows-1252');
4394
+
4395
+ for ($i = 0; $i < $len; $i++) {
4396
+ $c = $text[$i];
4397
+ $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
4398
+
4399
+ // check if we have to replace character
4400
+ if (isset($current_font['differences'][$char])) {
4401
+ $char = $current_font['differences'][$char];
4402
+ }
4403
+
4404
+ if (isset($current_font['C'][$char])) {
4405
+ $char_width = $current_font['C'][$char];
4406
+
4407
+ // add the character width
4408
+ $w += $char_width;
4409
+
4410
+ // add additional padding for space
4411
+ if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
4412
+ $w += $word_spacing * $space_scale;
4413
+ $n_spaces++;
4414
+ }
4415
+ }
4416
+ }
4417
+
4418
+ // add additional char spacing
4419
+ if ($char_spacing != 0) {
4420
+ $w += $char_spacing * $space_scale * ($len + $n_spaces);
4421
+ }
4422
+ }
4423
+
4424
+ $this->currentTextState = $store_currentTextState;
4425
+ $this->setCurrentFont();
4426
+
4427
+ return $w * $size / 1000;
4428
+ }
4429
+
4430
+ /**
4431
+ * this will be called at a new page to return the state to what it was on the
4432
+ * end of the previous page, before the stack was closed down
4433
+ * This is to get around not being able to have open 'q' across pages
4434
+ *
4435
+ * @param int $pageEnd
4436
+ */
4437
+ function saveState($pageEnd = 0)
4438
+ {
4439
+ if ($pageEnd) {
4440
+ // this will be called at a new page to return the state to what it was on the
4441
+ // end of the previous page, before the stack was closed down
4442
+ // This is to get around not being able to have open 'q' across pages
4443
+ $opt = $this->stateStack[$pageEnd];
4444
+ // ok to use this as stack starts numbering at 1
4445
+ $this->setColor($opt['col'], true);
4446
+ $this->setStrokeColor($opt['str'], true);
4447
+ $this->addContent("\n" . $opt['lin']);
4448
+ // $this->currentLineStyle = $opt['lin'];
4449
+ } else {
4450
+ $this->nStateStack++;
4451
+ $this->stateStack[$this->nStateStack] = array(
4452
+ 'col' => $this->currentColor,
4453
+ 'str' => $this->currentStrokeColor,
4454
+ 'lin' => $this->currentLineStyle
4455
+ );
4456
+ }
4457
+
4458
+ $this->save();
4459
+ }
4460
+
4461
+ /**
4462
+ * restore a previously saved state
4463
+ *
4464
+ * @param int $pageEnd
4465
+ */
4466
+ function restoreState($pageEnd = 0)
4467
+ {
4468
+ if (!$pageEnd) {
4469
+ $n = $this->nStateStack;
4470
+ $this->currentColor = $this->stateStack[$n]['col'];
4471
+ $this->currentStrokeColor = $this->stateStack[$n]['str'];
4472
+ $this->addContent("\n" . $this->stateStack[$n]['lin']);
4473
+ $this->currentLineStyle = $this->stateStack[$n]['lin'];
4474
+ $this->stateStack[$n] = null;
4475
+ unset($this->stateStack[$n]);
4476
+ $this->nStateStack--;
4477
+ }
4478
+
4479
+ $this->restore();
4480
+ }
4481
+
4482
+ /**
4483
+ * make a loose object, the output will go into this object, until it is closed, then will revert to
4484
+ * the current one.
4485
+ * this object will not appear until it is included within a page.
4486
+ * the function will return the object number
4487
+ *
4488
+ * @return int
4489
+ */
4490
+ function openObject()
4491
+ {
4492
+ $this->nStack++;
4493
+ $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
4494
+ // add a new object of the content type, to hold the data flow
4495
+ $this->numObj++;
4496
+ $this->o_contents($this->numObj, 'new');
4497
+ $this->currentContents = $this->numObj;
4498
+ $this->looseObjects[$this->numObj] = 1;
4499
+
4500
+ return $this->numObj;
4501
+ }
4502
+
4503
+ /**
4504
+ * open an existing object for editing
4505
+ *
4506
+ * @param $id
4507
+ */
4508
+ function reopenObject($id)
4509
+ {
4510
+ $this->nStack++;
4511
+ $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
4512
+ $this->currentContents = $id;
4513
+
4514
+ // also if this object is the primary contents for a page, then set the current page to its parent
4515
+ if (isset($this->objects[$id]['onPage'])) {
4516
+ $this->currentPage = $this->objects[$id]['onPage'];
4517
+ }
4518
+ }
4519
+
4520
+ /**
4521
+ * close an object
4522
+ */
4523
+ function closeObject()
4524
+ {
4525
+ // close the object, as long as there was one open in the first place, which will be indicated by
4526
+ // an objectId on the stack.
4527
+ if ($this->nStack > 0) {
4528
+ $this->currentContents = $this->stack[$this->nStack]['c'];
4529
+ $this->currentPage = $this->stack[$this->nStack]['p'];
4530
+ $this->nStack--;
4531
+ // easier to probably not worry about removing the old entries, they will be overwritten
4532
+ // if there are new ones.
4533
+ }
4534
+ }
4535
+
4536
+ /**
4537
+ * stop an object from appearing on pages from this point on
4538
+ *
4539
+ * @param $id
4540
+ */
4541
+ function stopObject($id)
4542
+ {
4543
+ // if an object has been appearing on pages up to now, then stop it, this page will
4544
+ // be the last one that could contain it.
4545
+ if (isset($this->addLooseObjects[$id])) {
4546
+ $this->addLooseObjects[$id] = '';
4547
+ }
4548
+ }
4549
+
4550
+ /**
4551
+ * after an object has been created, it wil only show if it has been added, using this function.
4552
+ *
4553
+ * @param $id
4554
+ * @param string $options
4555
+ */
4556
+ function addObject($id, $options = 'add')
4557
+ {
4558
+ // add the specified object to the page
4559
+ if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
4560
+ // then it is a valid object, and it is not being added to itself
4561
+ switch ($options) {
4562
+ case 'all':
4563
+ // then this object is to be added to this page (done in the next block) and
4564
+ // all future new pages.
4565
+ $this->addLooseObjects[$id] = 'all';
4566
+
4567
+ case 'add':
4568
+ if (isset($this->objects[$this->currentContents]['onPage'])) {
4569
+ // then the destination contents is the primary for the page
4570
+ // (though this object is actually added to that page)
4571
+ $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
4572
+ }
4573
+ break;
4574
+
4575
+ case 'even':
4576
+ $this->addLooseObjects[$id] = 'even';
4577
+ $pageObjectId = $this->objects[$this->currentContents]['onPage'];
4578
+ if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
4579
+ $this->addObject($id);
4580
+ // hacky huh :)
4581
+ }
4582
+ break;
4583
+
4584
+ case 'odd':
4585
+ $this->addLooseObjects[$id] = 'odd';
4586
+ $pageObjectId = $this->objects[$this->currentContents]['onPage'];
4587
+ if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
4588
+ $this->addObject($id);
4589
+ // hacky huh :)
4590
+ }
4591
+ break;
4592
+
4593
+ case 'next':
4594
+ $this->addLooseObjects[$id] = 'all';
4595
+ break;
4596
+
4597
+ case 'nexteven':
4598
+ $this->addLooseObjects[$id] = 'even';
4599
+ break;
4600
+
4601
+ case 'nextodd':
4602
+ $this->addLooseObjects[$id] = 'odd';
4603
+ break;
4604
+ }
4605
+ }
4606
+ }
4607
+
4608
+ /**
4609
+ * return a storable representation of a specific object
4610
+ *
4611
+ * @param $id
4612
+ * @return string|null
4613
+ */
4614
+ function serializeObject($id)
4615
+ {
4616
+ if (array_key_exists($id, $this->objects)) {
4617
+ return serialize($this->objects[$id]);
4618
+ }
4619
+
4620
+ return null;
4621
+ }
4622
+
4623
+ /**
4624
+ * restore an object from its stored representation. returns its new object id.
4625
+ *
4626
+ * @param $obj
4627
+ * @return int
4628
+ */
4629
+ function restoreSerializedObject($obj)
4630
+ {
4631
+ $obj_id = $this->openObject();
4632
+ $this->objects[$obj_id] = unserialize($obj);
4633
+ $this->closeObject();
4634
+
4635
+ return $obj_id;
4636
+ }
4637
+
4638
+ /**
4639
+ * add content to the documents info object
4640
+ *
4641
+ * @param $label
4642
+ * @param int $value
4643
+ */
4644
+ function addInfo($label, $value = 0)
4645
+ {
4646
+ // this will only work if the label is one of the valid ones.
4647
+ // modify this so that arrays can be passed as well.
4648
+ // if $label is an array then assume that it is key => value pairs
4649
+ // else assume that they are both scalar, anything else will probably error
4650
+ if (is_array($label)) {
4651
+ foreach ($label as $l => $v) {
4652
+ $this->o_info($this->infoObject, $l, $v);
4653
+ }
4654
+ } else {
4655
+ $this->o_info($this->infoObject, $label, $value);
4656
+ }
4657
+ }
4658
+
4659
+ /**
4660
+ * set the viewer preferences of the document, it is up to the browser to obey these.
4661
+ *
4662
+ * @param $label
4663
+ * @param int $value
4664
+ */
4665
+ function setPreferences($label, $value = 0)
4666
+ {
4667
+ // this will only work if the label is one of the valid ones.
4668
+ if (is_array($label)) {
4669
+ foreach ($label as $l => $v) {
4670
+ $this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v));
4671
+ }
4672
+ } else {
4673
+ $this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value));
4674
+ }
4675
+ }
4676
+
4677
+ /**
4678
+ * extract an integer from a position in a byte stream
4679
+ *
4680
+ * @param $data
4681
+ * @param $pos
4682
+ * @param $num
4683
+ * @return int
4684
+ */
4685
+ private function getBytes(&$data, $pos, $num)
4686
+ {
4687
+ // return the integer represented by $num bytes from $pos within $data
4688
+ $ret = 0;
4689
+ for ($i = 0; $i < $num; $i++) {
4690
+ $ret *= 256;
4691
+ $ret += ord($data[$pos + $i]);
4692
+ }
4693
+
4694
+ return $ret;
4695
+ }
4696
+
4697
+ /**
4698
+ * Check if image already added to pdf image directory.
4699
+ * If yes, need not to create again (pass empty data)
4700
+ *
4701
+ * @param $imgname
4702
+ * @return bool
4703
+ */
4704
+ function image_iscached($imgname)
4705
+ {
4706
+ return isset($this->imagelist[$imgname]);
4707
+ }
4708
+
4709
+ /**
4710
+ * add a PNG image into the document, from a GD object
4711
+ * this should work with remote files
4712
+ *
4713
+ * @param string $file The PNG file
4714
+ * @param float $x X position
4715
+ * @param float $y Y position
4716
+ * @param float $w Width
4717
+ * @param float $h Height
4718
+ * @param resource $img A GD resource
4719
+ * @param bool $is_mask true if the image is a mask
4720
+ * @param bool $mask true if the image is masked
4721
+ * @throws Exception
4722
+ */
4723
+ function addImagePng($file, $x, $y, $w = 0.0, $h = 0.0, &$img, $is_mask = false, $mask = null)
4724
+ {
4725
+ if (!function_exists("imagepng")) {
4726
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
4727
+ }
4728
+
4729
+ //if already cached, need not to read again
4730
+ if (isset($this->imagelist[$file])) {
4731
+ $data = null;
4732
+ } else {
4733
+ // Example for transparency handling on new image. Retain for current image
4734
+ // $tIndex = imagecolortransparent($img);
4735
+ // if ($tIndex > 0) {
4736
+ // $tColor = imagecolorsforindex($img, $tIndex);
4737
+ // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
4738
+ // imagefill($new_img, 0, 0, $new_tIndex);
4739
+ // imagecolortransparent($new_img, $new_tIndex);
4740
+ // }
4741
+ // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
4742
+ //imagealphablending($img, true);
4743
+
4744
+ //default, but explicitely set to ensure pdf compatibility
4745
+ imagesavealpha($img, false/*!$is_mask && !$mask*/);
4746
+
4747
+ $error = 0;
4748
+ //DEBUG_IMG_TEMP
4749
+ //debugpng
4750
+ if (defined("DEBUGPNG") && DEBUGPNG) {
4751
+ print '[addImagePng ' . $file . ']';
4752
+ }
4753
+
4754
+ ob_start();
4755
+ @imagepng($img);
4756
+ $data = ob_get_clean();
4757
+
4758
+ if ($data == '') {
4759
+ $error = 1;
4760
+ $errormsg = 'trouble writing file from GD';
4761
+ //DEBUG_IMG_TEMP
4762
+ //debugpng
4763
+ if (defined("DEBUGPNG") && DEBUGPNG) {
4764
+ print 'trouble writing file from GD';
4765
+ }
4766
+ }
4767
+
4768
+ if ($error) {
4769
+ $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
4770
+
4771
+ return;
4772
+ }
4773
+ } //End isset($this->imagelist[$file]) (png Duplicate removal)
4774
+
4775
+ $this->addPngFromBuf($file, $x, $y, $w, $h, $data, $is_mask, $mask);
4776
+ }
4777
+
4778
+ /**
4779
+ * @param $file
4780
+ * @param $x
4781
+ * @param $y
4782
+ * @param $w
4783
+ * @param $h
4784
+ * @param $byte
4785
+ */
4786
+ protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
4787
+ {
4788
+ // generate images
4789
+ $img = imagecreatefrompng($file);
4790
+
4791
+ if ($img === false) {
4792
+ return;
4793
+ }
4794
+
4795
+ // FIXME The pixel transformation doesn't work well with 8bit PNGs
4796
+ $eight_bit = ($byte & 4) !== 4;
4797
+
4798
+ $wpx = imagesx($img);
4799
+ $hpx = imagesy($img);
4800
+
4801
+ imagesavealpha($img, false);
4802
+
4803
+ // create temp alpha file
4804
+ $tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
4805
+ @unlink($tempfile_alpha);
4806
+ $tempfile_alpha = "$tempfile_alpha.png";
4807
+
4808
+ // create temp plain file
4809
+ $tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
4810
+ @unlink($tempfile_plain);
4811
+ $tempfile_plain = "$tempfile_plain.png";
4812
+
4813
+ $imgalpha = imagecreate($wpx, $hpx);
4814
+ imagesavealpha($imgalpha, false);
4815
+
4816
+ // generate gray scale palette (0 -> 255)
4817
+ for ($c = 0; $c < 256; ++$c) {
4818
+ imagecolorallocate($imgalpha, $c, $c, $c);
4819
+ }
4820
+
4821
+ // Use PECL gmagick + Graphics Magic to process transparent PNG images
4822
+ if (extension_loaded("gmagick")) {
4823
+ $gmagick = new \Gmagick($file);
4824
+ $gmagick->setimageformat('png');
4825
+
4826
+ // Get opacity channel (negative of alpha channel)
4827
+ $alpha_channel_neg = clone $gmagick;
4828
+ $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
4829
+
4830
+ // Negate opacity channel
4831
+ $alpha_channel = new \Gmagick();
4832
+ $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
4833
+ $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
4834
+ $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
4835
+ $alpha_channel->writeimage($tempfile_alpha);
4836
+
4837
+ // Cast to 8bit+palette
4838
+ $imgalpha_ = imagecreatefrompng($tempfile_alpha);
4839
+ imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
4840
+ imagedestroy($imgalpha_);
4841
+ imagepng($imgalpha, $tempfile_alpha);
4842
+
4843
+ // Make opaque image
4844
+ $color_channels = new \Gmagick();
4845
+ $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
4846
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
4847
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
4848
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
4849
+ $color_channels->writeimage($tempfile_plain);
4850
+
4851
+ $imgplain = imagecreatefrompng($tempfile_plain);
4852
+ }
4853
+ // Use PECL imagick + ImageMagic to process transparent PNG images
4854
+ elseif (extension_loaded("imagick")) {
4855
+ // Native cloning was added to pecl-imagick in svn commit 263814
4856
+ // the first version containing it was 3.0.1RC1
4857
+ static $imagickClonable = null;
4858
+ if ($imagickClonable === null) {
4859
+ $imagickClonable = version_compare(Imagick::IMAGICK_EXTVER, '3.0.1rc1') > 0;
4860
+ }
4861
+
4862
+ $imagick = new \Imagick($file);
4863
+ $imagick->setFormat('png');
4864
+
4865
+ // Get opacity channel (negative of alpha channel)
4866
+ if ($imagick->getImageAlphaChannel() !== 0) {
4867
+ $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
4868
+ $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
4869
+ // Since ImageMagick7 negate invert transparency as default
4870
+ $imagick_version = \Imagick::getVersion();
4871
+ if ( !empty($imagick_version) && is_array($imagick_version) && $imagick_version['versionNumber'] < 1800 ) {
4872
+ $alpha_channel->negateImage(true);
4873
+ }
4874
+ $alpha_channel->writeImage($tempfile_alpha);
4875
+
4876
+ // Cast to 8bit+palette
4877
+ $imgalpha_ = imagecreatefrompng($tempfile_alpha);
4878
+ imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
4879
+ imagedestroy($imgalpha_);
4880
+ imagepng($imgalpha, $tempfile_alpha);
4881
+ } else {
4882
+ $tempfile_alpha = null;
4883
+ }
4884
+
4885
+ // Make opaque image
4886
+ $color_channels = new \Imagick();
4887
+ $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
4888
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
4889
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
4890
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
4891
+ $color_channels->writeImage($tempfile_plain);
4892
+
4893
+ $imgplain = imagecreatefrompng($tempfile_plain);
4894
+ } else {
4895
+ // allocated colors cache
4896
+ $allocated_colors = array();
4897
+
4898
+ // extract alpha channel
4899
+ for ($xpx = 0; $xpx < $wpx; ++$xpx) {
4900
+ for ($ypx = 0; $ypx < $hpx; ++$ypx) {
4901
+ $color = imagecolorat($img, $xpx, $ypx);
4902
+ $col = imagecolorsforindex($img, $color);
4903
+ $alpha = $col['alpha'];
4904
+
4905
+ if ($eight_bit) {
4906
+ // with gamma correction
4907
+ $gammacorr = 2.2;
4908
+ $pixel = pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255;
4909
+ } else {
4910
+ // without gamma correction
4911
+ $pixel = (127 - $alpha) * 2;
4912
+
4913
+ $key = $col['red'] . $col['green'] . $col['blue'];
4914
+
4915
+ if (!isset($allocated_colors[$key])) {
4916
+ $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
4917
+ $allocated_colors[$key] = $pixel_img;
4918
+ } else {
4919
+ $pixel_img = $allocated_colors[$key];
4920
+ }
4921
+
4922
+ imagesetpixel($img, $xpx, $ypx, $pixel_img);
4923
+ }
4924
+
4925
+ imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
4926
+ }
4927
+ }
4928
+
4929
+ // extract image without alpha channel
4930
+ $imgplain = imagecreatetruecolor($wpx, $hpx);
4931
+ imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
4932
+ imagedestroy($img);
4933
+
4934
+ imagepng($imgalpha, $tempfile_alpha);
4935
+ imagepng($imgplain, $tempfile_plain);
4936
+ }
4937
+
4938
+ // embed mask image
4939
+ if ($tempfile_alpha) {
4940
+ $this->addImagePng($tempfile_alpha, $x, $y, $w, $h, $imgalpha, true);
4941
+ imagedestroy($imgalpha);
4942
+ }
4943
+
4944
+ // embed image, masked with previously embedded mask
4945
+ $this->addImagePng($tempfile_plain, $x, $y, $w, $h, $imgplain, false, ($tempfile_alpha !== null));
4946
+ imagedestroy($imgplain);
4947
+
4948
+ // remove temp files
4949
+ if ($tempfile_alpha) {
4950
+ unlink($tempfile_alpha);
4951
+ }
4952
+ unlink($tempfile_plain);
4953
+ }
4954
+
4955
+ /**
4956
+ * add a PNG image into the document, from a file
4957
+ * this should work with remote files
4958
+ *
4959
+ * @param $file
4960
+ * @param $x
4961
+ * @param $y
4962
+ * @param int $w
4963
+ * @param int $h
4964
+ * @throws Exception
4965
+ */
4966
+ function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
4967
+ {
4968
+ if (!function_exists("imagecreatefrompng")) {
4969
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
4970
+ }
4971
+
4972
+ //if already cached, need not to read again
4973
+ if (isset($this->imagelist[$file])) {
4974
+ $img = null;
4975
+ } else {
4976
+ $info = file_get_contents($file, false, null, 24, 5);
4977
+ $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
4978
+ $bit_depth = $meta["bitDepth"];
4979
+ $color_type = $meta["colorType"];
4980
+
4981
+ // http://www.w3.org/TR/PNG/#11IHDR
4982
+ // 3 => indexed
4983
+ // 4 => greyscale with alpha
4984
+ // 6 => fullcolor with alpha
4985
+ $is_alpha = in_array($color_type, array(4, 6)) || ($color_type == 3 && $bit_depth != 4);
4986
+
4987
+ if ($is_alpha) { // exclude grayscale alpha
4988
+ $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
4989
+ return;
4990
+ }
4991
+
4992
+ //png files typically contain an alpha channel.
4993
+ //pdf file format or class.pdf does not support alpha blending.
4994
+ //on alpha blended images, more transparent areas have a color near black.
4995
+ //This appears in the result on not storing the alpha channel.
4996
+ //Correct would be the box background image or its parent when transparent.
4997
+ //But this would make the image dependent on the background.
4998
+ //Therefore create an image with white background and copy in
4999
+ //A more natural background than black is white.
5000
+ //Therefore create an empty image with white background and merge the
5001
+ //image in with alpha blending.
5002
+ $imgtmp = @imagecreatefrompng($file);
5003
+ if (!$imgtmp) {
5004
+ return;
5005
+ }
5006
+ $sx = imagesx($imgtmp);
5007
+ $sy = imagesy($imgtmp);
5008
+ $img = imagecreatetruecolor($sx, $sy);
5009
+ imagealphablending($img, true);
5010
+
5011
+ // @todo is it still needed ??
5012
+ $ti = imagecolortransparent($imgtmp);
5013
+ if ($ti >= 0) {
5014
+ $tc = imagecolorsforindex($imgtmp, $ti);
5015
+ $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5016
+ imagefill($img, 0, 0, $ti);
5017
+ imagecolortransparent($img, $ti);
5018
+ } else {
5019
+ imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5020
+ }
5021
+
5022
+ imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5023
+ imagedestroy($imgtmp);
5024
+ }
5025
+ $this->addImagePng($file, $x, $y, $w, $h, $img);
5026
+
5027
+ if ($img) {
5028
+ imagedestroy($img);
5029
+ }
5030
+ }
5031
+
5032
+ /**
5033
+ * add a PNG image into the document, from a file
5034
+ * this should work with remote files
5035
+ *
5036
+ * @param $file
5037
+ * @param $x
5038
+ * @param $y
5039
+ * @param int $w
5040
+ * @param int $h
5041
+ */
5042
+ function addSvgFromFile($file, $x, $y, $w = 0, $h = 0)
5043
+ {
5044
+ $doc = new \Svg\Document();
5045
+ $doc->loadFile($file);
5046
+ $dimensions = $doc->getDimensions();
5047
+
5048
+ $this->save();
5049
+
5050
+ $this->transform(array($w / $dimensions["width"], 0, 0, $h / $dimensions["height"], $x, $y));
5051
+
5052
+ $surface = new \Svg\Surface\SurfaceCpdf($doc, $this);
5053
+ $doc->render($surface);
5054
+
5055
+ $this->restore();
5056
+ }
5057
+
5058
+ /**
5059
+ * add a PNG image into the document, from a memory buffer of the file
5060
+ *
5061
+ * @param $file
5062
+ * @param $x
5063
+ * @param $y
5064
+ * @param float $w
5065
+ * @param float $h
5066
+ * @param $data
5067
+ * @param bool $is_mask
5068
+ * @param null $mask
5069
+ */
5070
+ function addPngFromBuf($file, $x, $y, $w = 0.0, $h = 0.0, &$data, $is_mask = false, $mask = null)
5071
+ {
5072
+ if (isset($this->imagelist[$file])) {
5073
+ $data = null;
5074
+ $info['width'] = $this->imagelist[$file]['w'];
5075
+ $info['height'] = $this->imagelist[$file]['h'];
5076
+ $label = $this->imagelist[$file]['label'];
5077
+ } else {
5078
+ if ($data == null) {
5079
+ $this->addMessage('addPngFromBuf error - data not present!');
5080
+
5081
+ return;
5082
+ }
5083
+
5084
+ $error = 0;
5085
+
5086
+ if (!$error) {
5087
+ $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5088
+
5089
+ if (mb_substr($data, 0, 8, '8bit') != $header) {
5090
+ $error = 1;
5091
+
5092
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5093
+ print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5094
+ }
5095
+
5096
+ $errormsg = 'this file does not have a valid header';
5097
+ }
5098
+ }
5099
+
5100
+ if (!$error) {
5101
+ // set pointer
5102
+ $p = 8;
5103
+ $len = mb_strlen($data, '8bit');
5104
+
5105
+ // cycle through the file, identifying chunks
5106
+ $haveHeader = 0;
5107
+ $info = array();
5108
+ $idata = '';
5109
+ $pdata = '';
5110
+
5111
+ while ($p < $len) {
5112
+ $chunkLen = $this->getBytes($data, $p, 4);
5113
+ $chunkType = mb_substr($data, $p + 4, 4, '8bit');
5114
+
5115
+ switch ($chunkType) {
5116
+ case 'IHDR':
5117
+ // this is where all the file information comes from
5118
+ $info['width'] = $this->getBytes($data, $p + 8, 4);
5119
+ $info['height'] = $this->getBytes($data, $p + 12, 4);
5120
+ $info['bitDepth'] = ord($data[$p + 16]);
5121
+ $info['colorType'] = ord($data[$p + 17]);
5122
+ $info['compressionMethod'] = ord($data[$p + 18]);
5123
+ $info['filterMethod'] = ord($data[$p + 19]);
5124
+ $info['interlaceMethod'] = ord($data[$p + 20]);
5125
+
5126
+ //print_r($info);
5127
+ $haveHeader = 1;
5128
+ if ($info['compressionMethod'] != 0) {
5129
+ $error = 1;
5130
+
5131
+ //debugpng
5132
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5133
+ print '[addPngFromFile unsupported compression method ' . $file . ']';
5134
+ }
5135
+
5136
+ $errormsg = 'unsupported compression method';
5137
+ }
5138
+
5139
+ if ($info['filterMethod'] != 0) {
5140
+ $error = 1;
5141
+
5142
+ //debugpng
5143
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5144
+ print '[addPngFromFile unsupported filter method ' . $file . ']';
5145
+ }
5146
+
5147
+ $errormsg = 'unsupported filter method';
5148
+ }
5149
+ break;
5150
+
5151
+ case 'PLTE':
5152
+ $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5153
+ break;
5154
+
5155
+ case 'IDAT':
5156
+ $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5157
+ break;
5158
+
5159
+ case 'tRNS':
5160
+ //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
5161
+ //print "tRNS found, color type = ".$info['colorType']."\n";
5162
+ $transparency = array();
5163
+
5164
+ switch ($info['colorType']) {
5165
+ // indexed color, rbg
5166
+ case 3:
5167
+ /* corresponding to entries in the plte chunk
5168
+ Alpha for palette index 0: 1 byte
5169
+ Alpha for palette index 1: 1 byte
5170
+ ...etc...
5171
+ */
5172
+ // there will be one entry for each palette entry. up until the last non-opaque entry.
5173
+ // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
5174
+ $transparency['type'] = 'indexed';
5175
+ $trans = 0;
5176
+
5177
+ for ($i = $chunkLen; $i >= 0; $i--) {
5178
+ if (ord($data[$p + 8 + $i]) == 0) {
5179
+ $trans = $i;
5180
+ }
5181
+ }
5182
+
5183
+ $transparency['data'] = $trans;
5184
+ break;
5185
+
5186
+ // grayscale
5187
+ case 0:
5188
+ /* corresponding to entries in the plte chunk
5189
+ Gray: 2 bytes, range 0 .. (2^bitdepth)-1
5190
+ */
5191
+ // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
5192
+ $transparency['type'] = 'indexed';
5193
+ $transparency['data'] = ord($data[$p + 8 + 1]);
5194
+ break;
5195
+
5196
+ // truecolor
5197
+ case 2:
5198
+ /* corresponding to entries in the plte chunk
5199
+ Red: 2 bytes, range 0 .. (2^bitdepth)-1
5200
+ Green: 2 bytes, range 0 .. (2^bitdepth)-1
5201
+ Blue: 2 bytes, range 0 .. (2^bitdepth)-1
5202
+ */
5203
+ $transparency['r'] = $this->getBytes($data, $p + 8, 2);
5204
+ // r from truecolor
5205
+ $transparency['g'] = $this->getBytes($data, $p + 10, 2);
5206
+ // g from truecolor
5207
+ $transparency['b'] = $this->getBytes($data, $p + 12, 2);
5208
+ // b from truecolor
5209
+
5210
+ $transparency['type'] = 'color-key';
5211
+ break;
5212
+
5213
+ //unsupported transparency type
5214
+ default:
5215
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5216
+ print '[addPngFromFile unsupported transparency type ' . $file . ']';
5217
+ }
5218
+ break;
5219
+ }
5220
+
5221
+ // KS End new code
5222
+ break;
5223
+
5224
+ default:
5225
+ break;
5226
+ }
5227
+
5228
+ $p += $chunkLen + 12;
5229
+ }
5230
+
5231
+ if (!$haveHeader) {
5232
+ $error = 1;
5233
+
5234
+ //debugpng
5235
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5236
+ print '[addPngFromFile information header is missing ' . $file . ']';
5237
+ }
5238
+
5239
+ $errormsg = 'information header is missing';
5240
+ }
5241
+
5242
+ if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
5243
+ $error = 1;
5244
+
5245
+ //debugpng
5246
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5247
+ print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
5248
+ }
5249
+
5250
+ $errormsg = 'There appears to be no support for interlaced images in pdf.';
5251
+ }
5252
+ }
5253
+
5254
+ if (!$error && $info['bitDepth'] > 8) {
5255
+ $error = 1;
5256
+
5257
+ //debugpng
5258
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5259
+ print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
5260
+ }
5261
+
5262
+ $errormsg = 'only bit depth of 8 or less is supported';
5263
+ }
5264
+
5265
+ if (!$error) {
5266
+ switch ($info['colorType']) {
5267
+ case 3:
5268
+ $color = 'DeviceRGB';
5269
+ $ncolor = 1;
5270
+ break;
5271
+
5272
+ case 2:
5273
+ $color = 'DeviceRGB';
5274
+ $ncolor = 3;
5275
+ break;
5276
+
5277
+ case 0:
5278
+ $color = 'DeviceGray';
5279
+ $ncolor = 1;
5280
+ break;
5281
+
5282
+ default:
5283
+ $error = 1;
5284
+
5285
+ //debugpng
5286
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5287
+ print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
5288
+ }
5289
+
5290
+ $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
5291
+ }
5292
+ }
5293
+
5294
+ if ($error) {
5295
+ $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5296
+
5297
+ return;
5298
+ }
5299
+
5300
+ //print_r($info);
5301
+ // so this image is ok... add it in.
5302
+ $this->numImages++;
5303
+ $im = $this->numImages;
5304
+ $label = "I$im";
5305
+ $this->numObj++;
5306
+
5307
+ // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
5308
+ $options = array(
5309
+ 'label' => $label,
5310
+ 'data' => $idata,
5311
+ 'bitsPerComponent' => $info['bitDepth'],
5312
+ 'pdata' => $pdata,
5313
+ 'iw' => $info['width'],
5314
+ 'ih' => $info['height'],
5315
+ 'type' => 'png',
5316
+ 'color' => $color,
5317
+ 'ncolor' => $ncolor,
5318
+ 'masked' => $mask,
5319
+ 'isMask' => $is_mask
5320
+ );
5321
+
5322
+ if (isset($transparency)) {
5323
+ $options['transparency'] = $transparency;
5324
+ }
5325
+
5326
+ $this->o_image($this->numObj, 'new', $options);
5327
+ $this->imagelist[$file] = array('label' => $label, 'w' => $info['width'], 'h' => $info['height']);
5328
+ }
5329
+
5330
+ if ($is_mask) {
5331
+ return;
5332
+ }
5333
+
5334
+ if ($w <= 0 && $h <= 0) {
5335
+ $w = $info['width'];
5336
+ $h = $info['height'];
5337
+ }
5338
+
5339
+ if ($w <= 0) {
5340
+ $w = $h / $info['height'] * $info['width'];
5341
+ }
5342
+
5343
+ if ($h <= 0) {
5344
+ $h = $w * $info['height'] / $info['width'];
5345
+ }
5346
+
5347
+ $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
5348
+ }
5349
+
5350
+ /**
5351
+ * add a JPEG image into the document, from a file
5352
+ *
5353
+ * @param $img
5354
+ * @param $x
5355
+ * @param $y
5356
+ * @param int $w
5357
+ * @param int $h
5358
+ */
5359
+ function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
5360
+ {
5361
+ // attempt to add a jpeg image straight from a file, using no GD commands
5362
+ // note that this function is unable to operate on a remote file.
5363
+
5364
+ if (!file_exists($img)) {
5365
+ return;
5366
+ }
5367
+
5368
+ if ($this->image_iscached($img)) {
5369
+ $data = null;
5370
+ $imageWidth = $this->imagelist[$img]['w'];
5371
+ $imageHeight = $this->imagelist[$img]['h'];
5372
+ $channels = $this->imagelist[$img]['c'];
5373
+ } else {
5374
+ $tmp = getimagesize($img);
5375
+ $imageWidth = $tmp[0];
5376
+ $imageHeight = $tmp[1];
5377
+
5378
+ if (isset($tmp['channels'])) {
5379
+ $channels = $tmp['channels'];
5380
+ } else {
5381
+ $channels = 3;
5382
+ }
5383
+
5384
+ $data = file_get_contents($img);
5385
+ }
5386
+
5387
+ if ($w <= 0 && $h <= 0) {
5388
+ $w = $imageWidth;
5389
+ }
5390
+
5391
+ if ($w == 0) {
5392
+ $w = $h / $imageHeight * $imageWidth;
5393
+ }
5394
+
5395
+ if ($h == 0) {
5396
+ $h = $w * $imageHeight / $imageWidth;
5397
+ }
5398
+
5399
+ $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels, $img);
5400
+ }
5401
+
5402
+ /**
5403
+ * common code used by the two JPEG adding functions
5404
+ * @param $data
5405
+ * @param $x
5406
+ * @param $y
5407
+ * @param int $w
5408
+ * @param int $h
5409
+ * @param $imageWidth
5410
+ * @param $imageHeight
5411
+ * @param int $channels
5412
+ * @param $imgname
5413
+ */
5414
+ private function addJpegImage_common(
5415
+ &$data,
5416
+ $x,
5417
+ $y,
5418
+ $w = 0,
5419
+ $h = 0,
5420
+ $imageWidth,
5421
+ $imageHeight,
5422
+ $channels = 3,
5423
+ $imgname
5424
+ ) {
5425
+ if ($this->image_iscached($imgname)) {
5426
+ $label = $this->imagelist[$imgname]['label'];
5427
+ //debugpng
5428
+ //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
5429
+
5430
+ } else {
5431
+ if ($data == null) {
5432
+ $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
5433
+
5434
+ return;
5435
+ }
5436
+
5437
+ // note that this function is not to be called externally
5438
+ // it is just the common code between the GD and the file options
5439
+ $this->numImages++;
5440
+ $im = $this->numImages;
5441
+ $label = "I$im";
5442
+ $this->numObj++;
5443
+
5444
+ $this->o_image(
5445
+ $this->numObj,
5446
+ 'new',
5447
+ array(
5448
+ 'label' => $label,
5449
+ 'data' => &$data,
5450
+ 'iw' => $imageWidth,
5451
+ 'ih' => $imageHeight,
5452
+ 'channels' => $channels
5453
+ )
5454
+ );
5455
+
5456
+ $this->imagelist[$imgname] = array(
5457
+ 'label' => $label,
5458
+ 'w' => $imageWidth,
5459
+ 'h' => $imageHeight,
5460
+ 'c' => $channels
5461
+ );
5462
+ }
5463
+
5464
+ $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
5465
+ }
5466
+
5467
+ /**
5468
+ * specify where the document should open when it first starts
5469
+ *
5470
+ * @param $style
5471
+ * @param int $a
5472
+ * @param int $b
5473
+ * @param int $c
5474
+ */
5475
+ function openHere($style, $a = 0, $b = 0, $c = 0)
5476
+ {
5477
+ // this function will open the document at a specified page, in a specified style
5478
+ // the values for style, and the required parameters are:
5479
+ // 'XYZ' left, top, zoom
5480
+ // 'Fit'
5481
+ // 'FitH' top
5482
+ // 'FitV' left
5483
+ // 'FitR' left,bottom,right
5484
+ // 'FitB'
5485
+ // 'FitBH' top
5486
+ // 'FitBV' left
5487
+ $this->numObj++;
5488
+ $this->o_destination(
5489
+ $this->numObj,
5490
+ 'new',
5491
+ array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)
5492
+ );
5493
+ $id = $this->catalogId;
5494
+ $this->o_catalog($id, 'openHere', $this->numObj);
5495
+ }
5496
+
5497
+ /**
5498
+ * Add JavaScript code to the PDF document
5499
+ *
5500
+ * @param string $code
5501
+ */
5502
+ function addJavascript($code)
5503
+ {
5504
+ $this->javascript .= $code;
5505
+ }
5506
+
5507
+ /**
5508
+ * create a labelled destination within the document
5509
+ *
5510
+ * @param $label
5511
+ * @param $style
5512
+ * @param int $a
5513
+ * @param int $b
5514
+ * @param int $c
5515
+ */
5516
+ function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
5517
+ {
5518
+ // associates the given label with the destination, it is done this way so that a destination can be specified after
5519
+ // it has been linked to
5520
+ // styles are the same as the 'openHere' function
5521
+ $this->numObj++;
5522
+ $this->o_destination(
5523
+ $this->numObj,
5524
+ 'new',
5525
+ array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)
5526
+ );
5527
+ $id = $this->numObj;
5528
+
5529
+ // store the label->idf relationship, note that this means that labels can be used only once
5530
+ $this->destinations["$label"] = $id;
5531
+ }
5532
+
5533
+ /**
5534
+ * define font families, this is used to initialize the font families for the default fonts
5535
+ * and for the user to add new ones for their fonts. The default bahavious can be overridden should
5536
+ * that be desired.
5537
+ *
5538
+ * @param $family
5539
+ * @param string $options
5540
+ */
5541
+ function setFontFamily($family, $options = '')
5542
+ {
5543
+ if (!is_array($options)) {
5544
+ if ($family === 'init') {
5545
+ // set the known family groups
5546
+ // these font families will be used to enable bold and italic markers to be included
5547
+ // within text streams. html forms will be used... <b></b> <i></i>
5548
+ $this->fontFamilies['Helvetica.afm'] =
5549
+ array(
5550
+ 'b' => 'Helvetica-Bold.afm',
5551
+ 'i' => 'Helvetica-Oblique.afm',
5552
+ 'bi' => 'Helvetica-BoldOblique.afm',
5553
+ 'ib' => 'Helvetica-BoldOblique.afm'
5554
+ );
5555
+
5556
+ $this->fontFamilies['Courier.afm'] =
5557
+ array(
5558
+ 'b' => 'Courier-Bold.afm',
5559
+ 'i' => 'Courier-Oblique.afm',
5560
+ 'bi' => 'Courier-BoldOblique.afm',
5561
+ 'ib' => 'Courier-BoldOblique.afm'
5562
+ );
5563
+
5564
+ $this->fontFamilies['Times-Roman.afm'] =
5565
+ array(
5566
+ 'b' => 'Times-Bold.afm',
5567
+ 'i' => 'Times-Italic.afm',
5568
+ 'bi' => 'Times-BoldItalic.afm',
5569
+ 'ib' => 'Times-BoldItalic.afm'
5570
+ );
5571
+ }
5572
+ } else {
5573
+
5574
+ // the user is trying to set a font family
5575
+ // note that this can also be used to set the base ones to something else
5576
+ if (mb_strlen($family)) {
5577
+ $this->fontFamilies[$family] = $options;
5578
+ }
5579
+ }
5580
+ }
5581
+
5582
+ /**
5583
+ * used to add messages for use in debugging
5584
+ *
5585
+ * @param $message
5586
+ */
5587
+ function addMessage($message)
5588
+ {
5589
+ $this->messages .= $message . "\n";
5590
+ }
5591
+
5592
+ /**
5593
+ * a few functions which should allow the document to be treated transactionally.
5594
+ *
5595
+ * @param $action
5596
+ */
5597
+ function transaction($action)
5598
+ {
5599
+ switch ($action) {
5600
+ case 'start':
5601
+ // store all the data away into the checkpoint variable
5602
+ $data = get_object_vars($this);
5603
+ $this->checkpoint = $data;
5604
+ unset($data);
5605
+ break;
5606
+
5607
+ case 'commit':
5608
+ if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
5609
+ $tmp = $this->checkpoint['checkpoint'];
5610
+ $this->checkpoint = $tmp;
5611
+ unset($tmp);
5612
+ } else {
5613
+ $this->checkpoint = '';
5614
+ }
5615
+ break;
5616
+
5617
+ case 'rewind':
5618
+ // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
5619
+ if (is_array($this->checkpoint)) {
5620
+ // can only abort if were inside a checkpoint
5621
+ $tmp = $this->checkpoint;
5622
+
5623
+ foreach ($tmp as $k => $v) {
5624
+ if ($k !== 'checkpoint') {
5625
+ $this->$k = $v;
5626
+ }
5627
+ }
5628
+ unset($tmp);
5629
+ }
5630
+ break;
5631
+
5632
+ case 'abort':
5633
+ if (is_array($this->checkpoint)) {
5634
+ // can only abort if were inside a checkpoint
5635
+ $tmp = $this->checkpoint;
5636
+ foreach ($tmp as $k => $v) {
5637
+ $this->$k = $v;
5638
+ }
5639
+ unset($tmp);
5640
+ }
5641
+ break;
5642
+ }
5643
+ }
5644
+ }
woocommerce-pdf-invoices-packingslips.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: WooCommerce PDF Invoices & Packing Slips
4
  * Plugin URI: http://www.wpovernight.com
5
  * Description: Create, print & email PDF invoices & packing slips for WooCommerce orders.
6
- * Version: 2.5.1
7
  * Author: Ewout Fernhout
8
  * Author URI: http://www.wpovernight.com
9
  * License: GPLv2 or later
@@ -21,7 +21,7 @@ if ( !class_exists( 'WPO_WCPDF' ) ) :
21
 
22
  class WPO_WCPDF {
23
 
24
- public $version = '2.5.1';
25
  public $plugin_basename;
26
  public $legacy_mode;
27
 
3
  * Plugin Name: WooCommerce PDF Invoices & Packing Slips
4
  * Plugin URI: http://www.wpovernight.com
5
  * Description: Create, print & email PDF invoices & packing slips for WooCommerce orders.
6
+ * Version: 2.5.2
7
  * Author: Ewout Fernhout
8
  * Author URI: http://www.wpovernight.com
9
  * License: GPLv2 or later
21
 
22
  class WPO_WCPDF {
23
 
24
+ public $version = '2.5.2';
25
  public $plugin_basename;
26
  public $legacy_mode;
27