##// END OF EJS Templates
Merged r9853 from trunk (#10688)...
Toshi MARUYAMA -
r9672:668981b8cd0e
parent child
Show More
@@ -1,2017 +1,2018
1 1 == Redmine changelog
2 2
3 3 Redmine - project management software
4 4 Copyright (C) 2006-2012 Jean-Philippe Lang
5 5 http://www.redmine.org/
6 6
7 7 == TBD v1.4.4
8 8
9 * Defect #10688: PDF export from Wiki - Problems with tables
9 10 * Defect #11160: SQL Error on time report if a custom field has multiple values for an entry
10 11 * Defect #11061: Cannot choose commit versions to view differences in Git/Mercurial repository view
11 12 * Defect #11112: REST API - custom fields in POST/PUT ignored for time_entries
12 13 * Defect #11133: Wiki-page section edit link can point to incorrect section
13 14 * Defect #11178: Spent time sorted by date-descending order lists same-date entries in physical order
14 15 * Feature #6597: Configurable session lifetime and timeout
15 16 * Patch #11113: Small glitch in German localization
16 17 * Fix for Rails vulnerabilities CVE-2012-2694 and CVE-2012-2695
17 18
18 19 == 2012-06-05 v1.4.3
19 20
20 21 * Defect #11038: "Create and continue" should preserve project, issue and activity when logging time
21 22 * Defect #11046: Redmine.pm does not support "bind as user" ldap authentication
22 23 * Defect #11051: reposman.rb fails in 1.4.2 because of missing require for rubygems
23 24 * Fix for Rails vulnerability CVE-2012-2660
24 25
25 26 == 2012-05-13 v1.4.2
26 27
27 28 * Defect #10744: rake task redmine:email:test broken
28 29 * Defect #10787: "Allow users to unsubscribe" option is confusing
29 30 * Defect #10827: Cannot access Repositories page and Settings in a Project - Error 500
30 31 * Defect #10829: db:migrate fails 0.8.2 -> 1.4.1
31 32 * Defect #10832: REST Uploads fail with fastcgi
32 33 * Defect #10837: reposman and rdm-mailhandler not working with ruby 1.9.x
33 34 * Defect #10856: can not load translations from hr.yml with ruby1.9.3-p194
34 35 * Defect #10865: Filter reset when deleting locked user
35 36 * Feature #9790: Allow filtering text custom fields on "is null" and "is not null"
36 37 * Feature #10778: svn:ignore for config/additional_environment.rb
37 38 * Feature #10875: Partial Albanian Translations
38 39 * Feature #10888: Bring back List-Id to help aid Gmail filtering
39 40 * Patch #10733: Traditional Chinese language file (to r9502)
40 41 * Patch #10745: Japanese translation update (r9519)
41 42 * Patch #10750: Swedish Translation for r9522
42 43 * Patch #10785: Bulgarian translation (jstoolbar)
43 44 * Patch #10800: Simplified Chinese translation
44 45
45 46 == 2012-04-20 v1.4.1
46 47
47 48 * Defect #8574: Time report: date range fields not enabled when using the calendar popup
48 49 * Defect #10642: Nested textile ol/ul lists generate invalid HTML
49 50 * Defect #10668: RSS key is generated twice when user is not reloaded
50 51 * Defect #10669: Token.destroy_expired should not delete API tokens
51 52 * Defect #10675: "Submit and continue" is broken
52 53 * Defect #10711: User cannot change account details with "Login has already been taken" error
53 54 * Feature #10664: Unsubscribe Own User Account
54 55 * Patch #10693: German Translation Update
55 56
56 57 == 2012-04-14 v1.4.0
57 58
58 59 * Defect #2719: Increase username length limit from 30 to 60
59 60 * Defect #3087: Revision referring to issues across all projects
60 61 * Defect #4824: Unable to connect (can't convert Net::LDAP::LdapError into String)
61 62 * Defect #5058: reminder mails are not sent when delivery_method is :async_smtp
62 63 * Defect #6859: Moving issues to a tracker with different custom fields should let fill these fields
63 64 * Defect #7398: Error when trying to quick create a version with required custom field
64 65 * Defect #7495: Python multiline comments highlighting problem in Repository browser
65 66 * Defect #7826: bigdecimal-segfault-fix.rb must be removed for Oracle
66 67 * Defect #7920: Attempted to update a stale object when copying a project
67 68 * Defect #8857: Git: Too long in fetching repositories after upgrade from 1.1 or new branch at first time
68 69 * Defect #9472: The git scm module causes an excess amount of DB traffic.
69 70 * Defect #9685: Adding multiple times the same related issue relation is possible
70 71 * Defect #9798: Release 1.3.0 does not detect rubytree under ruby 1.9.3p0 / rails 2.3.14
71 72 * Defect #9978: Japanese "permission_add_issue_watchers" is wrong
72 73 * Defect #10006: Email reminders are sent for closed issues
73 74 * Defect #10150: CSV export and spent time: rounding issue
74 75 * Defect #10168: CSV export breaks custom columns
75 76 * Defect #10181: Issue context menu and bulk edit form show irrelevant statuses
76 77 * Defect #10198: message_id regex in pop3.rb only recognizes Message-ID header (not Message-Id)
77 78 * Defect #10251: Description diff link in note details is relative when received by email
78 79 * Defect #10272: Ruby 1.9.3: "incompatible character encoding" with LDAP auth
79 80 * Defect #10275: Message object not passed to wiki macros for head topic and in preview edit mode
80 81 * Defect #10334: Full name is not unquoted when creating users from emails
81 82 * Defect #10410: [Localization] Grammar issue of Simplified Chinese in zh.yml
82 83 * Defect #10442: Ruby 1.9.3 Time Zone setting Internal error.
83 84 * Defect #10467: Confusing behavior while moving issue to a project with disabled Issues module
84 85 * Defect #10575: Uploading of attachments which filename contains non-ASCII chars fails with Ruby 1.9
85 86 * Defect #10590: WikiContent::Version#text return string with #<Encoding:ASCII-8BIT> when uncompressed
86 87 * Defect #10593: Error: 'incompatible character encodings: UTF-8 and ASCII-8BIT' (old annoing issue) on ruby-1.9.3
87 88 * Defect #10600: Watchers search generates an Internal error
88 89 * Defect #10605: Bulk edit selected issues does not allow selection of blank values for custom fields
89 90 * Defect #10619: When changing status before tracker, it shows improper status
90 91 * Feature #779: Multiple SCM per project
91 92 * Feature #971: Add "Spent time" column to query
92 93 * Feature #1060: Add a LDAP-filter using external auth sources
93 94 * Feature #1102: Shortcut for assigning an issue to me
94 95 * Feature #1189: Multiselect custom fields
95 96 * Feature #1363: Allow underscores in project identifiers
96 97 * Feature #1913: LDAP - authenticate as user
97 98 * Feature #1972: Attachments for News
98 99 * Feature #2009: Manually add related revisions
99 100 * Feature #2323: Workflow permissions for administrators
100 101 * Feature #2416: {background:color} doesn't work in text formatting
101 102 * Feature #2694: Notification on loosing assignment
102 103 * Feature #2715: "Magic links" to notes
103 104 * Feature #2850: Add next/previous navigation to issue
104 105 * Feature #3055: Option to copy attachments when copying an issue
105 106 * Feature #3108: set parent automatically for new pages
106 107 * Feature #3463: Export all wiki pages to PDF
107 108 * Feature #4050: Ruby 1.9 support
108 109 * Feature #4769: Ability to move an issue to a different project from the update form
109 110 * Feature #4774: Change the hyperlink for file attachment to view and download
110 111 * Feature #5159: Ability to add Non-Member watchers to the watch list
111 112 * Feature #5638: Use Bundler (Gemfile) for gem management
112 113 * Feature #5643: Add X-Redmine-Sender header to email notifications
113 114 * Feature #6296: Bulk-edit custom fields through context menu
114 115 * Feature #6386: Issue mail should render the HTML version of the issue details
115 116 * Feature #6449: Edit a wiki page's parent on the edit page
116 117 * Feature #6555: Double-click on "Submit" and "Save" buttons should not send two requests to server
117 118 * Feature #7361: Highlight active query in the side bar
118 119 * Feature #7420: Rest API for projects members
119 120 * Feature #7603: Please make editing issues more obvious than "Change properties (More)"
120 121 * Feature #8171: Adding attachments through the REST API
121 122 * Feature #8691: Better handling of issue update conflict
122 123 * Feature #9803: Change project through REST API issue update
123 124 * Feature #9923: User type custom fields should be filterable by "Me".
124 125 * Feature #9985: Group time report by the Status field
125 126 * Feature #9995: Time entries insertion, "Create and continue" button
126 127 * Feature #10020: Enable global time logging at /time_entries/new
127 128 * Feature #10042: Bulk change private flag
128 129 * Feature #10126: Add members of subprojects in the assignee and author filters
129 130 * Feature #10131: Include custom fiels in time entries API responses
130 131 * Feature #10207: Git: use default branch from HEAD
131 132 * Feature #10208: Estonian translation
132 133 * Feature #10253: Better handling of attachments when validation fails
133 134 * Feature #10350: Bulk copy should allow for changing the target version
134 135 * Feature #10607: Ignore out-of-office incoming emails
135 136 * Feature #10635: Adding time like "123 Min" is invalid
136 137 * Patch #9998: Make attachement "Optional Description" less wide
137 138 * Patch #10066: i18n not working with russian gem
138 139 * Patch #10128: Disable IE 8 compatibility mode to fix wrong div.autoscroll scroll bar behaviour
139 140 * Patch #10155: Russian translation changed
140 141 * Patch #10464: Enhanced PDF output for Issues list
141 142 * Patch #10470: Efficiently process new git revisions in a single batch
142 143 * Patch #10513: Dutch translation improvement
143 144
144 145 == 2012-04-14 v1.3.3
145 146
146 147 * Defect #10505: Error when exporting to PDF with NoMethodError (undefined method `downcase' for nil:NilClass)
147 148 * Defect #10554: Defect symbols when exporting tasks in pdf
148 149 * Defect #10564: Unable to change locked, sticky flags and board when editing a message
149 150 * Defect #10591: Dutch "label_file_added" translation is wrong
150 151 * Defect #10622: "Default administrator account changed" is always true
151 152 * Patch #10555: rake redmine:send_reminders aborted if issue assigned to group
152 153 * Patch #10611: Simplified Chinese translations for 1.3-stable
153 154
154 155 == 2012-03-11 v1.3.2
155 156
156 157 * Defect #8194: {{toc}} uses identical anchors for subsections with the same name
157 158 * Defect #9143: Partial diff comparison should be done on actual code, not on html
158 159 * Defect #9523: {{toc}} does not display headers with @ code markup
159 160 * Defect #9815: Release 1.3.0 does not detect rubytree with rubgems 1.8
160 161 * Defect #10053: undefined method `<=>' for nil:NilClass when accessing the settings of a project
161 162 * Defect #10135: ActionView::TemplateError (can't convert Fixnum into String)
162 163 * Defect #10193: Unappropriate icons in highlighted code block
163 164 * Defect #10199: No wiki section edit when title contains code
164 165 * Defect #10218: Error when creating a project with a version custom field
165 166 * Defect #10241: "get version by ID" fails with "401 not authorized" error when using API access key
166 167 * Defect #10284: Note added by commit from a subproject does not contain project identifier
167 168 * Defect #10374: User list is empty when adding users to project / group if remaining users are added late
168 169 * Defect #10390: Mass assignment security vulnerability
169 170 * Patch #8413: Confirmation message before deleting a relationship
170 171 * Patch #10160: Bulgarian translation (r8777)
171 172 * Patch #10242: Migrate Redmine.pm from Digest::Sha1 to Digest::Sha
172 173 * Patch #10258: Italian translation for 1.3-stable
173 174
174 175 == 2012-02-06 v1.3.1
175 176
176 177 * Defect #9775: app/views/repository/_revision_graph.html.erb sets window.onload directly..
177 178 * Defect #9792: Ruby 1.9: [v1.3.0] Error: incompatible character encodings for it translation on Calendar page
178 179 * Defect #9793: Bad spacing between numbered list and heading (recently broken).
179 180 * Defect #9795: Unrelated error message when creating a group with an invalid name
180 181 * Defect #9832: Revision graph height should depend on height of rows in revisions table
181 182 * Defect #9937: Repository settings are not saved when all SCM are disabled
182 183 * Defect #9961: Ukrainian "default_tracker_bug" is wrong
183 184 * Defect #10013: Rest API - Create Version -> Internal server error 500
184 185 * Defect #10115: Javascript error - Can't attach more than 1 file on IE 6 and 7
185 186 * Defect #10130: Broken italic text style in edited comment preview
186 187 * Defect #10152: Attachment diff type is not saved in user preference
187 188 * Feature #9943: Arabic translation
188 189 * Patch #9874: pt-BR translation updates
189 190 * Patch #9922: Spanish translation updated
190 191 * Patch #10137: Korean language file ko.yml updated to Redmine 1.3.0
191 192
192 193 == 2011-12-10 v1.3.0
193 194
194 195 * Defect #2109: Context menu is being submitted twice per right click
195 196 * Defect #7717: MailHandler user creation for unknown_user impossible due to diverging length-limits of login and email fields
196 197 * Defect #7917: Creating users via email fails if user real name containes special chars
197 198 * Defect #7966: MailHandler does not include JournalDetail for attached files
198 199 * Defect #8368: Bad decimal separator in time entry CSV
199 200 * Defect #8371: MySQL error when filtering a custom field using the REST api
200 201 * Defect #8549: Export CSV has character encoding error
201 202 * Defect #8573: Do not show inactive Enumerations where not needed
202 203 * Defect #8611: rake/rdoctask is deprecated
203 204 * Defect #8751: Email notification: bug, when number of recipients more then 8
204 205 * Defect #8894: Private issues - make it more obvious in the UI?
205 206 * Defect #8994: Hardcoded French string "anonyme"
206 207 * Defect #9043: Hardcoded string "diff" in Wiki#show and Repositories_Helper
207 208 * Defect #9051: wrong "text_issue_added" in russian translation.
208 209 * Defect #9108: Custom query not saving status filter
209 210 * Defect #9252: Regression: application title escaped 2 times
210 211 * Defect #9264: Bad Portuguese translation
211 212 * Defect #9470: News list is missing Avatars
212 213 * Defect #9471: Inline markup broken in Wiki link labels
213 214 * Defect #9489: Label all input field and control tags
214 215 * Defect #9534: Precedence: bulk email header is non standard and discouraged
215 216 * Defect #9540: Issue filter by assigned_to_role is not project specific
216 217 * Defect #9619: Time zone ignored when logging time while editing ticket
217 218 * Defect #9638: Inconsistent image filename extensions
218 219 * Defect #9669: Issue list doesn't sort assignees/authors regarding user display format
219 220 * Defect #9672: Message-quoting in forums module broken
220 221 * Defect #9719: Filtering by numeric custom field types broken after update to master
221 222 * Defect #9724: Can't remote add new categories
222 223 * Defect #9738: Setting of cross-project custom query is not remembered inside project
223 224 * Defect #9748: Error about configuration.yml validness should mention file path
224 225 * Feature #69: Textilized description in PDF
225 226 * Feature #401: Add pdf export for WIKI page
226 227 * Feature #1567: Make author column sortable and groupable
227 228 * Feature #2222: Single section edit.
228 229 * Feature #2269: Default issue start date should become configurable.
229 230 * Feature #2371: character encoding for attachment file
230 231 * Feature #2964: Ability to assign issues to groups
231 232 * Feature #3033: Bug Reporting: Using "Create and continue" should show bug id of saved bug
232 233 * Feature #3261: support attachment images in PDF export
233 234 * Feature #4264: Update CodeRay to 1.0 final
234 235 * Feature #4324: Redmine renames my files, it shouldn't.
235 236 * Feature #4729: Add Date-Based Filters for Issues List
236 237 * Feature #4742: CSV export: option to export selected or all columns
237 238 * Feature #4976: Allow rdm-mailhandler to read the API key from a file
238 239 * Feature #5501: Git: Mercurial: Adding visual merge/branch history to repository view
239 240 * Feature #5634: Export issue to PDF does not include Subtasks and Related Issues
240 241 * Feature #5670: Cancel option for file upload
241 242 * Feature #5737: Custom Queries available through the REST Api
242 243 * Feature #6180: Searchable custom fields do not provide adequate operators
243 244 * Feature #6954: Filter from date to date
244 245 * Feature #7180: List of statuses in REST API
245 246 * Feature #7181: List of trackers in REST API
246 247 * Feature #7366: REST API for Issue Relations
247 248 * Feature #7403: REST API for Versions
248 249 * Feature #7671: REST API for reading attachments
249 250 * Feature #7832: Ability to assign issue categories to groups
250 251 * Feature #8420: Consider removing #7013 workaround
251 252 * Feature #9196: Improve logging in MailHandler when user creation fails
252 253 * Feature #9496: Adds an option in mailhandler to disable server certificate verification
253 254 * Feature #9553: CRUD operations for "Issue categories" in REST API
254 255 * Feature #9593: HTML title should be reordered
255 256 * Feature #9600: Wiki links for news and forums
256 257 * Feature #9607: Filter for issues without start date (or any another field based on date type)
257 258 * Feature #9609: Upgrade to Rails 2.3.14
258 259 * Feature #9612: "side by side" and "inline" patch view for attachments
259 260 * Feature #9667: Check attachment size before upload
260 261 * Feature #9690: Link in notification pointing to the actual update
261 262 * Feature #9720: Add note number for single issue's PDF
262 263 * Patch #8617: Indent subject of subtask ticket in exported issues PDF
263 264 * Patch #8778: Traditional Chinese 'issue' translation change
264 265 * Patch #9053: Fix up Russian translation
265 266 * Patch #9129: Improve wording of Git repository note at project setting
266 267 * Patch #9148: Better handling of field_due_date italian translation
267 268 * Patch #9273: Fix typos in russian localization
268 269 * Patch #9484: Limit SCM annotate to text files under the maximum file size for viewing
269 270 * Patch #9659: Indexing rows in auth_sources/index view
270 271 * Patch #9692: Fix Textilized description in PDF for CodeRay
271 272
272 273 == 2011-12-10 v1.2.3
273 274
274 275 * Defect #8707: Reposman: wrong constant name
275 276 * Defect #8809: Table in timelog report overflows
276 277 * Defect #9055: Version files in Files module cannot be downloaded if issue tracking is disabled
277 278 * Defect #9137: db:encrypt fails to handle repositories with blank password
278 279 * Defect #9394: Custom date field only validating on regex and not a valid date
279 280 * Defect #9405: Any user with :log_time permission can edit time entries via context menu
280 281 * Defect #9448: The attached images are not shown in documents
281 282 * Defect #9520: Copied private query not visible after project copy
282 283 * Defect #9552: Error when reading ciphered text from the database without cipher key configured
283 284 * Defect #9566: Redmine.pm considers all projects private when login_required is enabled
284 285 * Defect #9567: Redmine.pm potential security issue with cache credential enabled and subversion
285 286 * Defect #9577: Deleting a subtasks doesn't update parent's rgt & lft values
286 287 * Defect #9597: Broken version links in wiki annotate history
287 288 * Defect #9682: Wiki HTML Export only useful when Access history is accessible
288 289 * Defect #9737: Custom values deleted before issue submit
289 290 * Defect #9741: calendar-hr.js (Croatian) is not UTF-8
290 291 * Patch #9558: Simplified Chinese translation for 1.2.2 updated
291 292 * Patch #9695: Bulgarian translation (r7942)
292 293
293 294 == 2011-11-11 v1.2.2
294 295
295 296 * Defect #3276: Incorrect handling of anchors in Wiki to HTML export
296 297 * Defect #7215: Wiki formatting mangles links to internal headers
297 298 * Defect #7613: Generated test instances may share the same attribute value object
298 299 * Defect #8411: Can't remove "Project" column on custom query
299 300 * Defect #8615: Custom 'version' fields don't show shared versions
300 301 * Defect #8633: Pagination counts non visible issues
301 302 * Defect #8651: Email attachments are not added to issues any more in v1.2
302 303 * Defect #8825: JRuby + Windows: SCMs do not work on Redmine 1.2
303 304 * Defect #8836: Additional workflow transitions not available when set to both author and assignee
304 305 * Defect #8865: Custom field regular expression is not validated
305 306 * Defect #8880: Error deleting issue with grandchild
306 307 * Defect #8884: Assignee is cleared when updating issue with locked assignee
307 308 * Defect #8892: Unused fonts in rfpdf plugin folder
308 309 * Defect #9161: pt-BR field_warn_on_leaving_unsaved has a small gramatical error
309 310 * Defect #9308: Search fails when a role haven't "view wiki" permission
310 311 * Defect #9465: Mercurial: can't browse named branch below Mercurial 1.5
311 312
312 313 == 2011-07-11 v1.2.1
313 314
314 315 * Defect #5089: i18N error on truncated revision diff view
315 316 * Defect #7501: Search options get lost after clicking on a specific result type
316 317 * Defect #8229: "project.xml" response does not include the parent ID
317 318 * Defect #8449: Wiki annotated page does not display author of version 1
318 319 * Defect #8467: Missing german translation - Warn me when leaving a page with unsaved text
319 320 * Defect #8468: No warning when leaving page with unsaved text that has not lost focus
320 321 * Defect #8472: Private checkbox ignored on issue creation with "Set own issues public or private" permission
321 322 * Defect #8510: JRuby: Can't open administrator panel if scm command is not available
322 323 * Defect #8512: Syntax highlighter on Welcome page
323 324 * Defect #8554: Translation missing error on custom field validation
324 325 * Defect #8565: JRuby: Japanese PDF export error
325 326 * Defect #8566: Exported PDF UTF-8 Vietnamese not correct
326 327 * Defect #8569: JRuby: PDF export error with TypeError
327 328 * Defect #8576: Missing german translation - different things
328 329 * Defect #8616: Circular relations
329 330 * Defect #8646: Russian translation "label_follows" and "label_follows" are wrong
330 331 * Defect #8712: False 'Description updated' journal details messages
331 332 * Defect #8729: Not-public queries are not private
332 333 * Defect #8737: Broken line of long issue description on issue PDF.
333 334 * Defect #8738: Missing revision number/id of associated revisions on issue PDF
334 335 * Defect #8739: Workflow copy does not copy advanced workflow settings
335 336 * Defect #8759: Setting issue attributes from mail should be case-insensitive
336 337 * Defect #8777: Mercurial: Not able to Resetting Redmine project respository
337 338
338 339 == 2011-05-30 v1.2.0
339 340
340 341 * Defect #61: Broken character encoding in pdf export
341 342 * Defect #1965: Redmine is not Tab Safe
342 343 * Defect #2274: Filesystem Repository path encoding of non UTF-8 characters
343 344 * Defect #2664: Mercurial: Repository path encoding of non UTF-8 characters
344 345 * Defect #3421: Mercurial reads files from working dir instead of changesets
345 346 * Defect #3462: CVS: Repository path encoding of non UTF-8 characters
346 347 * Defect #3715: Login page should not show projects link and search box if authentication is required
347 348 * Defect #3724: Mercurial repositories display revision ID instead of changeset ID
348 349 * Defect #3761: Most recent CVS revisions are missing in "revisions" view
349 350 * Defect #4270: CVS Repository view in Project doesn't show Author, Revision, Comment
350 351 * Defect #5138: Don't use Ajax for pagination
351 352 * Defect #5152: Cannot use certain characters for user and role names.
352 353 * Defect #5251: Git: Repository path encoding of non UTF-8 characters
353 354 * Defect #5373: Translation missing when adding invalid watchers
354 355 * Defect #5817: Shared versions not shown in subproject's gantt chart
355 356 * Defect #6013: git tab,browsing, very slow -- even after first time
356 357 * Defect #6148: Quoting, newlines, and nightmares...
357 358 * Defect #6256: Redmine considers non ASCII and UTF-16 text files as binary in SCM
358 359 * Defect #6476: Subproject's issues are not shown in the subproject's gantt
359 360 * Defect #6496: Remove i18n 0.3.x/0.4.x hack for Rails 2.3.5
360 361 * Defect #6562: Context-menu deletion of issues deletes all subtasks too without explicit prompt
361 362 * Defect #6604: Issues targeted at parent project versions' are not shown on gantt chart
362 363 * Defect #6706: Resolving issues with the commit message produces the wrong comment with CVS
363 364 * Defect #6901: Copy/Move an issue does not give any history of who actually did the action.
364 365 * Defect #6905: Specific heading-content breaks CSS
365 366 * Defect #7000: Project filter not applied on versions in Gantt chart
366 367 * Defect #7097: Starting day of week cannot be set to Saturday
367 368 * Defect #7114: New gantt doesn't display some projects
368 369 * Defect #7146: Git adapter lost commits before 7 days from database latest changeset
369 370 * Defect #7218: Date range error on issue query
370 371 * Defect #7257: "Issues by" version links bad criterias
371 372 * Defect #7279: CSS class ".icon-home" is not used.
372 373 * Defect #7320: circular dependency >2 issues
373 374 * Defect #7352: Filters not working in Gantt charts
374 375 * Defect #7367: Receiving pop3 email should not output debug messages
375 376 * Defect #7373: Error with PDF output and ruby 1.9.2
376 377 * Defect #7379: Remove extraneous hidden_field on wiki history
377 378 * Defect #7516: Redmine does not work with RubyGems 1.5.0
378 379 * Defect #7518: Mercurial diff can be wrong if the previous changeset isn't the parent
379 380 * Defect #7581: Not including a spent time value on the main issue update screen causes silent data loss
380 381 * Defect #7582: hiding form pages from search engines
381 382 * Defect #7597: Subversion and Mercurial log have the possibility to miss encoding
382 383 * Defect #7604: ActionView::TemplateError (undefined method `name' for nil:NilClass)
383 384 * Defect #7605: Using custom queries always redirects to "Issues" tab
384 385 * Defect #7615: CVS diffs do not handle new files properly
385 386 * Defect #7618: SCM diffs do not handle one line new files properly
386 387 * Defect #7639: Some date fields do not have requested format.
387 388 * Defect #7657: Wrong commit range in git log command on Windows
388 389 * Defect #7818: Wiki pages don't use the local timezone to display the "Updated ? hours ago" mouseover
389 390 * Defect #7821: Git "previous" and "next" revisions are incorrect
390 391 * Defect #7827: CVS: Age column on repository view is off by timezone delta
391 392 * Defect #7843: Add a relation between issues = explicit login window ! (basic authentication popup is prompted on AJAX request)
392 393 * Defect #8011: {{toc}} does not display headlines with inline code markup
393 394 * Defect #8029: List of users for adding to a group may be empty if 100 first users have been added
394 395 * Defect #8064: Text custom fields do not wrap on the issue list
395 396 * Defect #8071: Watching a subtask from the context menu updates main issue watch link
396 397 * Defect #8072: Two untranslatable default role names
397 398 * Defect #8075: Some "notifiable" names are not i18n-enabled
398 399 * Defect #8081: GIT: Commits missing when user has the "decorate" git option enabled
399 400 * Defect #8088: Colorful indentation of subprojects must be on right in RTL locales
400 401 * Defect #8239: notes field is not propagated during issue copy
401 402 * Defect #8356: GET /time_entries.xml ignores limit/offset parameters
402 403 * Defect #8432: Private issues information shows up on Activity page for unauthorized users
403 404 * Feature #746: Versioned issue descriptions
404 405 * Feature #1067: Differentiate public/private saved queries in the sidebar
405 406 * Feature #1236: Make destination folder for attachment uploads configurable
406 407 * Feature #1735: Per project repository log encoding setting
407 408 * Feature #1763: Autologin-cookie should be configurable
408 409 * Feature #1981: display mercurial tags
409 410 * Feature #2074: Sending email notifications when comments are added in the news section
410 411 * Feature #2096: Custom fields referencing system tables (users and versions)
411 412 * Feature #2732: Allow additional workflow transitions for author and assignee
412 413 * Feature #2910: Warning on leaving edited issue/wiki page without saving
413 414 * Feature #3396: Git: use --encoding=UTF-8 in "git log"
414 415 * Feature #4273: SCM command availability automatic check in administration panel
415 416 * Feature #4477: Use mime types in downloading from repository
416 417 * Feature #5518: Graceful fallback for "missing translation" needed
417 418 * Feature #5520: Text format buttons and preview link missing when editing comment
418 419 * Feature #5831: Parent Task to Issue Bulk Edit
419 420 * Feature #6887: Upgrade to Rails 2.3.11
420 421 * Feature #7139: Highlight changes inside diff lines
421 422 * Feature #7236: Collapse All for Groups
422 423 * Feature #7246: Handle "named branch" for mercurial
423 424 * Feature #7296: Ability for admin to delete users
424 425 * Feature #7318: Add user agent to Redmine Mailhandler
425 426 * Feature #7408: Add an application configuration file
426 427 * Feature #7409: Cross project Redmine links
427 428 * Feature #7410: Add salt to user passwords
428 429 * Feature #7411: Option to cipher LDAP ans SCM passwords stored in the database
429 430 * Feature #7412: Add an issue visibility level to each role
430 431 * Feature #7414: Private issues
431 432 * Feature #7517: Configurable path of executable for scm adapters
432 433 * Feature #7640: Add "mystery man" gravatar to options
433 434 * Feature #7858: RubyGems 1.6 support
434 435 * Feature #7893: Group filter on the users list
435 436 * Feature #7899: Box for editing comments should open with the formatting toolbar
436 437 * Feature #7921: issues by pulldown should have 'status' option
437 438 * Feature #7996: Bulk edit and context menu for time entries
438 439 * Feature #8006: Right click context menu for Related Issues
439 440 * Feature #8209: I18n YAML files not parsable with psych yaml library
440 441 * Feature #8345: Link to user profile from account page
441 442 * Feature #8365: Git: per project setting to report last commit or not in repository tree
442 443 * Patch #5148: metaKey not handled in issues selection
443 444 * Patch #5629: Wrap text fields properly in PDF
444 445 * Patch #7418: Redmine Persian Translation
445 446 * Patch #8295: Wrap title fields properly in PDF
446 447 * Patch #8310: fixes automatic line break problem with TCPDF
447 448 * Patch #8312: Switch to TCPDF from FPDF for PDF export
448 449
449 450 == 2011-04-29 v1.1.3
450 451
451 452 * Defect #5773: Email reminders are sent to locked users
452 453 * Defect #6590: Wrong file list link in email notification on new file upload
453 454 * Defect #7589: Wiki page with backslash in title can not be found
454 455 * Defect #7785: Mailhandler keywords are not removed when updating issues
455 456 * Defect #7794: Internal server error on formatting an issue as a PDF in Japanese
456 457 * Defect #7838: Gantt- Issues does not show up in green when start and end date are the same
457 458 * Defect #7846: Headers (h1, etc.) containing backslash followed by a digit are not displayed correctly
458 459 * Defect #7875: CSV export separators in polish locale (pl.yml)
459 460 * Defect #7890: Internal server error when referencing an issue without project in commit message
460 461 * Defect #7904: Subprojects not properly deleted when deleting a parent project
461 462 * Defect #7939: Simultaneous Wiki Updates Cause Internal Error
462 463 * Defect #7951: Atom links broken on wiki index
463 464 * Defect #7954: IE 9 can not select issues, does not display context menu
464 465 * Defect #7985: Trying to do a bulk edit results in "Internal Error"
465 466 * Defect #8003: Error raised by reposman.rb under Windows server 2003
466 467 * Defect #8012: Wrong selection of modules when adding new project after validation error
467 468 * Defect #8038: Associated Revisions OL/LI items are not styled properly in issue view
468 469 * Defect #8067: CSV exporting in Italian locale
469 470 * Defect #8235: bulk edit issues and copy issues error in es, gl and ca locales
470 471 * Defect #8244: selected modules are not activated when copying a project
471 472 * Patch #7278: Update Simplified Chinese translation to 1.1
472 473 * Patch #7390: Fixes in Czech localization
473 474 * Patch #7963: Reminder email: Link for show all issues does not sort
474 475
475 476 == 2011-03-07 v1.1.2
476 477
477 478 * Defect #3132: Bulk editing menu non-functional in Opera browser
478 479 * Defect #6090: Most binary files become corrupted when downloading from CVS repository browser when Redmine is running on a Windows server
479 480 * Defect #7280: Issues subjects wrap in Gantt
480 481 * Defect #7288: Non ASCII filename downloaded from repo is broken on Internet Explorer.
481 482 * Defect #7317: Gantt tab gives internal error due to nil avatar icon
482 483 * Defect #7497: Aptana Studio .project file added to version 1.1.1-stable
483 484 * Defect #7611: Workflow summary shows X icon for workflow with exactly 1 status transition
484 485 * Defect #7625: Syntax highlighting unavailable from board new topic or topic edit preview
485 486 * Defect #7630: Spent time in commits not recognized
486 487 * Defect #7656: MySQL SQL Syntax Error when filtering issues by Assignee's Group
487 488 * Defect #7718: Minutes logged in commit message are converted to hours
488 489 * Defect #7763: Email notification are sent to watchers even if 'No events' setting is chosen
489 490 * Feature #7608: Add "retro" gravatars
490 491 * Patch #7598: Extensible MailHandler
491 492 * Patch #7795: Internal server error at journals#index with custom fields
492 493
493 494 == 2011-01-30 v1.1.1
494 495
495 496 * Defect #4899: Redmine fails to list files for darcs repository
496 497 * Defect #7245: Wiki fails to find pages with cyrillic characters using postgresql
497 498 * Defect #7256: redmine/public/.htaccess must be moved for non-fastcgi installs/upgrades
498 499 * Defect #7258: Automatic spent time logging does not work properly with SQLite3
499 500 * Defect #7259: Released 1.1.0 uses "devel" label inside admin information
500 501 * Defect #7265: "Loading..." icon does not disappear after add project member
501 502 * Defect #7266: Test test_due_date_distance_in_words fail due to undefined locale
502 503 * Defect #7274: CSV value separator in dutch locale
503 504 * Defect #7277: Enabling gravatas causes usernames to overlap first name field in user list
504 505 * Defect #7294: "Notifiy for only project I select" is not available anymore in 1.1.0
505 506 * Defect #7307: HTTP 500 error on query for empty revision
506 507 * Defect #7313: Label not translated in french in Settings/Email Notification tab
507 508 * Defect #7329: <code class="javascript"> with long strings may hang server
508 509 * Defect #7337: My page french translation
509 510 * Defect #7348: French Translation of "Connection"
510 511 * Defect #7385: Error when viewing an issue which was related to a deleted subtask
511 512 * Defect #7386: NoMethodError on pdf export
512 513 * Defect #7415: Darcs adapter recognizes new files as modified files above Darcs 2.4
513 514 * Defect #7421: no email sent with 'Notifiy for any event on the selected projects only'
514 515 * Feature #5344: Update to latest CodeRay 0.9.x
515 516
516 517 == 2011-01-09 v1.1.0
517 518
518 519 * Defect #2038: Italics in wiki headers show-up wrong in the toc
519 520 * Defect #3449: Redmine Takes Too Long On Large Mercurial Repository
520 521 * Defect #3567: Sorting for changesets might go wrong on Mercurial repos
521 522 * Defect #3707: {{toc}} doesn't work with {{include}}
522 523 * Defect #5096: Redmine hangs up while browsing Git repository
523 524 * Defect #6000: Safe Attributes prevents plugin extension of Issue model...
524 525 * Defect #6064: Modules not assigned to projects created via API
525 526 * Defect #6110: MailHandler should allow updating Issue Priority and Custom fields
526 527 * Defect #6136: JSON API holds less information than XML API
527 528 * Defect #6345: xml used by rest API is invalid
528 529 * Defect #6348: Gantt chart PDF rendering errors
529 530 * Defect #6403: Updating an issue with custom fields fails
530 531 * Defect #6467: "Member of role", "Member of group" filter not work correctly
531 532 * Defect #6473: New gantt broken after clearing issue filters
532 533 * Defect #6541: Email notifications send to everybody
533 534 * Defect #6549: Notification settings not migrated properly
534 535 * Defect #6591: Acronyms must have a minimum of three characters
535 536 * Defect #6674: Delete time log broken after changes to REST
536 537 * Defect #6681: Mercurial, Bazaar and Darcs auto close issue text should be commit id instead of revision number
537 538 * Defect #6724: Wiki uploads does not work anymore (SVN 4266)
538 539 * Defect #6746: Wiki links are broken on Activity page
539 540 * Defect #6747: Wiki diff does not work since r4265
540 541 * Defect #6763: New gantt charts: subject displayed twice on issues
541 542 * Defect #6826: Clicking "Add" twice creates duplicate member record
542 543 * Defect #6844: Unchecking status filter on the issue list has no effect
543 544 * Defect #6895: Wrong Polish translation of "blocks"
544 545 * Defect #6943: Migration from boolean to varchar fails on PostgreSQL 8.1
545 546 * Defect #7064: Mercurial adapter does not recognize non alphabetic nor numeric in UTF-8 copied files
546 547 * Defect #7128: New gantt chart does not render subtasks under parent task
547 548 * Defect #7135: paging mechanism returns the same last page forever
548 549 * Defect #7188: Activity page not refreshed when changing language
549 550 * Defect #7195: Apply CLI-supplied defaults for incoming mail only to new issues not replies
550 551 * Defect #7197: Tracker reset to default when replying to an issue email
551 552 * Defect #7213: Copy project does not copy all roles and permissions
552 553 * Defect #7225: Project settings: Trackers & Custom fields only relevant if module Issue tracking is active
553 554 * Feature #630: Allow non-unique names for projects
554 555 * Feature #1738: Add a "Visible" flag to project/user custom fields
555 556 * Feature #2803: Support for Javascript in Themes
556 557 * Feature #2852: Clean Incoming Email of quoted text "----- Reply above this line ------"
557 558 * Feature #2995: Improve error message when trying to access an archived project
558 559 * Feature #3170: Autocomplete issue relations on subject
559 560 * Feature #3503: Administrator Be Able To Modify Email settings Of Users
560 561 * Feature #4155: Automatic spent time logging from commit messages
561 562 * Feature #5136: Parent select on Wiki rename page
562 563 * Feature #5338: Descendants (subtasks) should be available via REST API
563 564 * Feature #5494: Wiki TOC should display heading from level 4
564 565 * Feature #5594: Improve MailHandler's keyword handling
565 566 * Feature #5622: Allow version to be set via incoming email
566 567 * Feature #5712: Reload themes
567 568 * Feature #5869: Issue filters by Group and Role
568 569 * Feature #6092: Truncate Git revision labels in Activity page/feed and allow configurable length
569 570 * Feature #6112: Accept localized keywords when receiving emails
570 571 * Feature #6140: REST issues response with issue count limit and offset
571 572 * Feature #6260: REST API for Users
572 573 * Feature #6276: Gantt Chart rewrite
573 574 * Feature #6446: Remove length limits on project identifier and name
574 575 * Feature #6628: Improvements in truncate email
575 576 * Feature #6779: Project JSON API
576 577 * Feature #6823: REST API for time tracker.
577 578 * Feature #7072: REST API for news
578 579 * Feature #7111: Expose more detail on journal entries
579 580 * Feature #7141: REST API: get information about current user
580 581 * Patch #4807: Allow to set the done_ratio field with the incoming mail system
581 582 * Patch #5441: Initialize TimeEntry attributes with params[:time_entry]
582 583 * Patch #6762: Use GET instead of POST to retrieve context_menu
583 584 * Patch #7160: French translation ofr "not_a_date" is missing
584 585 * Patch #7212: Missing remove_index in AddUniqueIndexOnMembers down migration
585 586
586 587
587 588 == 2010-12-23 v1.0.5
588 589
589 590 * #6656: Mercurial adapter loses seconds of commit times
590 591 * #6996: Migration trac(sqlite3) -> redmine(postgresql) doesnt escape ' char
591 592 * #7013: v-1.0.4 trunk - see {{count}} in page display rather than value
592 593 * #7016: redundant 'field_start_date' in ja.yml
593 594 * #7018: 'undefined method `reschedule_after' for nil:NilClass' on new issues
594 595 * #7024: E-mail notifications about Wiki changes.
595 596 * #7033: 'class' attribute of <pre> tag shouldn't be truncate
596 597 * #7035: CSV value separator in russian
597 598 * #7122: Issue-description Quote-button missing
598 599 * #7144: custom queries making use of deleted custom fields cause a 500 error
599 600 * #7162: Multiply defined label in french translation
600 601
601 602 == 2010-11-28 v1.0.4
602 603
603 604 * #5324: Git not working if color.ui is enabled
604 605 * #6447: Issues API doesn't allow full key auth for all actions
605 606 * #6457: Edit User group problem
606 607 * #6575: start date being filled with current date even when blank value is submitted
607 608 * #6740: Max attachment size, incorrect usage of 'KB'
608 609 * #6760: Select box sorted by ID instead of name in Issue Category
609 610 * #6766: Changing target version name can cause an internal error
610 611 * #6784: Redmine not working with i18n gem 0.4.2
611 612 * #6839: Hardcoded absolute links in my/page_layout
612 613 * #6841: Projects API doesn't allow full key auth for all actions
613 614 * #6860: svn: Write error: Broken pipe when browsing repository
614 615 * #6874: API should return XML description when creating a project
615 616 * #6932: submitting wrong parent task input creates a 500 error
616 617 * #6966: Records of Forums are remained, deleting project
617 618 * #6990: Layout problem in workflow overview
618 619 * #5117: mercurial_adapter should ensure the right LANG environment variable
619 620 * #6782: Traditional Chinese language file (to r4352)
620 621 * #6783: Swedish Translation for r4352
621 622 * #6804: Bugfix: spelling fixes
622 623 * #6814: Japanese Translation for r4362
623 624 * #6948: Bulgarian translation
624 625 * #6973: Update es.yml
625 626
626 627 == 2010-10-31 v1.0.3
627 628
628 629 * #4065: Redmine.pm doesn't work with LDAPS and a non-standard port
629 630 * #4416: Link from version details page to edit the wiki.
630 631 * #5484: Add new issue as subtask to an existing ticket
631 632 * #5948: Update help/wiki_syntax_detailed.html with more link options
632 633 * #6494: Typo in pt_BR translation for 1.0.2
633 634 * #6508: Japanese translation update
634 635 * #6509: Localization pt-PT (new strings)
635 636 * #6511: Rake task to test email
636 637 * #6525: Traditional Chinese language file (to r4225)
637 638 * #6536: Patch for swedish translation
638 639 * #6548: Rake tasks to add/remove i18n strings
639 640 * #6569: Updated Hebrew translation
640 641 * #6570: Japanese Translation for r4231
641 642 * #6596: pt-BR translation updates
642 643 * #6629: Change field-name of issues start date
643 644 * #6669: Bulgarian translation
644 645 * #6731: Macedonian translation fix
645 646 * #6732: Japanese Translation for r4287
646 647 * #6735: Add user-agent to reposman
647 648 * #6736: Traditional Chinese language file (to r4288)
648 649 * #6739: Swedish Translation for r4288
649 650 * #6765: Traditional Chinese language file (to r4302)
650 651 * Fixed #5324: Git not working if color.ui is enabled
651 652 * Fixed #5652: Bad URL parsing in the wiki when it ends with right-angle-bracket(greater-than mark).
652 653 * Fixed #5803: Precedes/Follows Relationships Broke
653 654 * Fixed #6435: Links to wikipages bound to versions do not respect version-sharing in Settings -> Versions
654 655 * Fixed #6438: Autologin cannot be disabled again once it's enabled
655 656 * Fixed #6513: "Move" and "Copy" are not displayed when deployed in subdirectory
656 657 * Fixed #6521: Tooltip/label for user "search-refinment" field on group/project member list
657 658 * Fixed #6563: i18n-issues on calendar view
658 659 * Fixed #6598: Wrong caption for button_create_and_continue in German language file
659 660 * Fixed #6607: Unclear caption for german button_update
660 661 * Fixed #6612: SortHelper missing from CalendarsController
661 662 * Fixed #6740: Max attachment size, incorrect usage of 'KB'
662 663 * Fixed #6750: ActionView::TemplateError (undefined method `empty?' for nil:NilClass) on line #12 of app/views/context_menus/issues.html.erb:
663 664
664 665 == 2010-09-26 v1.0.2
665 666
666 667 * #2285: issue-refinement: pressing enter should result to an "apply"
667 668 * #3411: Allow mass status update trough context menu
668 669 * #5929: https-enabled gravatars when called over https
669 670 * #6189: Japanese Translation for r4011
670 671 * #6197: Traditional Chinese language file (to r4036)
671 672 * #6198: Updated german translation
672 673 * #6208: Macedonian translation
673 674 * #6210: Swedish Translation for r4039
674 675 * #6248: nl translation update for r4050
675 676 * #6263: Catalan translation update
676 677 * #6275: After submitting a related issue, the Issue field should be re-focused
677 678 * #6289: Checkboxes in issues list shouldn't be displayed when printing
678 679 * #6290: Make journals theming easier
679 680 * #6291: User#allowed_to? is not tested
680 681 * #6306: Traditional Chinese language file (to r4061)
681 682 * #6307: Korean translation update for 4066(4061)
682 683 * #6316: pt_BR update
683 684 * #6339: SERBIAN Updated
684 685 * #6358: Updated Polish translation
685 686 * #6363: Japanese Translation for r4080
686 687 * #6365: Traditional Chinese language file (to r4081)
687 688 * #6382: Issue PDF export variable usage
688 689 * #6428: Interim solution for i18n >= 0.4
689 690 * #6441: Japanese Translation for r4162
690 691 * #6451: Traditional Chinese language file (to r4167)
691 692 * #6465: Japanese Translation for r4171
692 693 * #6466: Traditional Chinese language file (to r4171)
693 694 * #6490: pt-BR translation for 1.0.2
694 695 * Fixed #3935: stylesheet_link_tag with plugin doesn't take into account relative_url_root
695 696 * Fixed #4998: Global issue list's context menu has enabled options for parent menus but there are no valid selections
696 697 * Fixed #5170: Done ratio can not revert to 0% if status is used for done ratio
697 698 * Fixed #5608: broken with i18n 0.4.0
698 699 * Fixed #6054: Error 500 on filenames with whitespace in git reposities
699 700 * Fixed #6135: Default logger configuration grows without bound.
700 701 * Fixed #6191: Deletion of a main task deletes all subtasks
701 702 * Fixed #6195: Missing move issues between projects
702 703 * Fixed #6242: can't switch between inline and side-by-side diff
703 704 * Fixed #6249: Create and continue returns 404
704 705 * Fixed #6267: changing the authentication mode from ldap to internal with setting the password
705 706 * Fixed #6270: diff coderay malformed in the "news" page
706 707 * Fixed #6278: missing "cant_link_an_issue_with_a_descendant"from locale files
707 708 * Fixed #6333: Create and continue results in a 404 Error
708 709 * Fixed #6346: Age column on repository view is skewed for git, probably CVS too
709 710 * Fixed #6351: Context menu on roadmap broken
710 711 * Fixed #6388: New Subproject leads to a 404
711 712 * Fixed #6392: Updated/Created links to activity broken
712 713 * Fixed #6413: Error in SQL
713 714 * Fixed #6443: Redirect to project settings after Copying a Project
714 715 * Fixed #6448: Saving a wiki page with no content has a translation missing
715 716 * Fixed #6452: Unhandled exception on creating File
716 717 * Fixed #6471: Typo in label_report in Czech translation
717 718 * Fixed #6479: Changing tracker type will lose watchers
718 719 * Fixed #6499: Files with leading or trailing whitespace are not shown in git.
719 720
720 721 == 2010-08-22 v1.0.1
721 722
722 723 * #819: Add a body ID and class to all pages
723 724 * #871: Commit new CSS styles!
724 725 * #3301: Add favicon to base layout
725 726 * #4656: On Issue#show page, clicking on Ò€œAdd related issueҀ� should focus on the input
726 727 * #4896: Project identifier should be a limited field
727 728 * #5084: Filter all isssues by projects
728 729 * #5477: Replace Test::Unit::TestCase with ActiveSupport::TestCase
729 730 * #5591: 'calendar' action is used with 'issue' controller in issue/sidebar
730 731 * #5735: Traditional Chinese language file (to r3810)
731 732 * #5740: Swedish Translation for r3810
732 733 * #5785: pt-BR translation update
733 734 * #5898: Projects should be displayed as links in users/memberships
734 735 * #5910: Chinese translation to redmine-1.0.0
735 736 * #5912: Translation update for french locale
736 737 * #5962: Hungarian translation update to r3892
737 738 * #5971: Remove falsly applied chrome on revision links
738 739 * #5972: Updated Hebrew translation for 1.0.0
739 740 * #5982: Updated german translation
740 741 * #6008: Move admin_menu to Redmine::MenuManager
741 742 * #6012: RTL layout
742 743 * #6021: Spanish translation 1.0.0-RC
743 744 * #6025: nl translation updated for r3905
744 745 * #6030: Japanese Translation for r3907
745 746 * #6074: sr-CY.yml contains DOS-type newlines (\r\n)
746 747 * #6087: SERBIAN translation updated
747 748 * #6093: Updated italian translation
748 749 * #6142: Swedish Translation for r3940
749 750 * #6153: Move view_calendar and view_gantt to own modules
750 751 * #6169: Add issue status to issue tooltip
751 752 * Fixed #3834: Add a warning when not choosing a member role
752 753 * Fixed #3922: Bad english arround "Assigned to" text in journal entries
753 754 * Fixed #5158: Simplified Chinese language file zh.yml updated to r3608
754 755 * Fixed #5162: translation missing: zh-TW, field_time_entrie
755 756 * Fixed #5297: openid not validated correctly
756 757 * Fixed #5628: Wrong commit range in git log command
757 758 * Fixed #5760: Assigned_to and author filters in "Projects>View all issues" should be based on user's project visibility
758 759 * Fixed #5771: Problem when importing git repository
759 760 * Fixed #5775: ldap authentication in admin menu should have an icon
760 761 * Fixed #5811: deleting statuses doesnt delete workflow entries
761 762 * Fixed #5834: Emails with trailing spaces incorrectly detected as invalid
762 763 * Fixed #5846: ChangeChangesPathLengthLimit does not remove default for MySQL
763 764 * Fixed #5861: Vertical scrollbar always visible in Wiki "code" blocks in Chrome.
764 765 * Fixed #5883: correct label_project_latest Chinese translation
765 766 * Fixed #5892: Changing status from contextual menu opens the ticket instead
766 767 * Fixed #5904: Global gantt PDF and PNG should display project names
767 768 * Fixed #5925: parent task's priority edit should be disabled through shortcut menu in issues list page
768 769 * Fixed #5935: Add Another file to ticket doesn't work in IE Internet Explorer
769 770 * Fixed #5937: Harmonize french locale "zero" translation with other locales
770 771 * Fixed #5945: Forum message permalinks don't take pagination into account
771 772 * Fixed #5978: Debug code still remains
772 773 * Fixed #6009: When using "English (British)", the repository browser (svn) shows files over 1000 bytes as floating point (2.334355)
773 774 * Fixed #6045: Repository file Diff view sometimes shows more than selected file
774 775 * Fixed #6079: German Translation error in TimeEntryActivity
775 776 * Fixed #6100: User's profile should display all visible projects
776 777 * Fixed #6132: Allow Key based authentication in the Boards atom feed
777 778 * Fixed #6163: Bad CSS class for calendar project menu_item
778 779 * Fixed #6172: Browsing to a missing user's page shows the admin sidebar
779 780
780 781 == 2010-07-18 v1.0.0 (Release candidate)
781 782
782 783 * #443: Adds context menu to the roadmap issue lists
783 784 * #443: Subtasking
784 785 * #741: Description preview while editing an issue
785 786 * #1131: Add support for alternate (non-LDAP) authentication
786 787 * #1214: REST API for Issues
787 788 * #1223: File upload on wiki edit form
788 789 * #1755: add "blocked by" as a related issues option
789 790 * #2420: Fetching emails from an POP server
790 791 * #2482: Named scopes in Issue and ActsAsWatchable plus some view refactoring (logic extraction).
791 792 * #2924: Make the right click menu more discoverable using a cursor property
792 793 * #2985: Make syntax highlighting pluggable
793 794 * #3201: Workflow Check/Uncheck All Rows/Columns
794 795 * #3359: Update CodeRay 0.9
795 796 * #3706: Allow assigned_to field configuration on Issue creation by email
796 797 * #3936: configurable list of models to include in search
797 798 * #4480: Create a link to the user profile from the administration interface
798 799 * #4482: Cache textile rendering
799 800 * #4572: Make it harder to ruin your database
800 801 * #4573: Move github gems to Gemcutter
801 802 * #4664: Add pagination to forum threads
802 803 * #4732: Make login case-insensitive also for PostgreSQL
803 804 * #4812: Create links to other projects
804 805 * #4819: Replace images with smushed ones for speed
805 806 * #4945: Allow custom fields attached to project to be searchable
806 807 * #5121: Fix issues list layout overflow
807 808 * #5169: Issue list view hook request
808 809 * #5208: Aibility to edit wiki sidebar
809 810 * #5281: Remove empty ul tags in the issue history
810 811 * #5291: Updated basque translations
811 812 * #5328: Automatically add "Repository" menu_item after repository creation
812 813 * #5415: Fewer SQL statements generated for watcher_recipients
813 814 * #5416: Exclude "fields_for" from overridden methods in TabularFormBuilder
814 815 * #5573: Allow issue assignment in email
815 816 * #5595: Allow start date and due dates to be set via incoming email
816 817 * #5752: The projects view (/projects) renders ul's wrong
817 818 * #5781: Allow to use more macros on the welcome page and project list
818 819 * Fixed #1288: Unable to past escaped wiki syntax in an issue description
819 820 * Fixed #1334: Wiki formatting character *_ and _*
820 821 * Fixed #1416: Inline code with less-then/greater-than produces @lt; and @gt; respectively
821 822 * Fixed #2473: Login and mail should not be case sensitive
822 823 * Fixed #2990: Ruby 1.9 - wrong number of arguments (1 for 0) on rake db:migrate
823 824 * Fixed #3089: Text formatting sometimes breaks when combined
824 825 * Fixed #3690: Status change info duplicates on the issue screen
825 826 * Fixed #3691: Redmine allows two files with the same file name to be uploaded to the same issue
826 827 * Fixed #3764: ApplicationHelperTest fails with JRuby
827 828 * Fixed #4265: Unclosed code tags in issue descriptions affects main UI
828 829 * Fixed #4745: Bug in index.xml.builder (issues)
829 830 * Fixed #4852: changing user/roles of project member not possible without javascript
830 831 * Fixed #4857: Week number calculation in date picker is wrong if a week starts with Sunday
831 832 * Fixed #4883: Bottom "contextual" placement in issue with associated changeset
832 833 * Fixed #4918: Revisions r3453 and r3454 broke On-the-fly user creation with LDAP
833 834 * Fixed #4935: Navigation to the Master Timesheet page (time_entries)
834 835 * Fixed #5043: Flash messages are not displayed after the project settings[module/activity] saved
835 836 * Fixed #5081: Broken links on public/help/wiki_syntax_detailed.html
836 837 * Fixed #5104: Description of document not wikified on documents index
837 838 * Fixed #5108: Issue linking fails inside of []s
838 839 * Fixed #5199: diff code coloring using coderay
839 840 * Fixed #5233: Add a hook to the issue report (Summary) view
840 841 * Fixed #5265: timetracking: subtasks time is added to the main task
841 842 * Fixed #5343: acts_as_event Doesn't Accept Outside URLs
842 843 * Fixed #5440: UI Inconsistency : Administration > Enumerations table row headers should be enclosed in <thead>
843 844 * Fixed #5463: 0.9.4 INSTALL and/or UPGRADE, missing session_store.rb
844 845 * Fixed #5524: Update_parent_attributes doesn't work for the old parent issue when reparenting
845 846 * Fixed #5548: SVN Repository: Can not list content of a folder which includes square brackets.
846 847 * Fixed #5589: "with subproject" malfunction
847 848 * Fixed #5676: Search for Numeric Value
848 849 * Fixed #5696: Redmine + PostgreSQL 8.4.4 fails on _dir_list_content.rhtml
849 850 * Fixed #5698: redmine:email:receive_imap fails silently for mails with subject longer than 255 characters
850 851 * Fixed #5700: TimelogController#destroy assumes success
851 852 * Fixed #5751: developer role is mispelled
852 853 * Fixed #5769: Popup Calendar doesn't Advance in Chrome
853 854 * Fixed #5771: Problem when importing git repository
854 855 * Fixed #5823: Error in comments in plugin.rb
855 856
856 857
857 858 == 2010-07-07 v0.9.6
858 859
859 860 * Fixed: Redmine.pm access by unauthorized users
860 861
861 862 == 2010-06-24 v0.9.5
862 863
863 864 * Linkify folder names on revision view
864 865 * "fiters" and "options" should be hidden in print view via css
865 866 * Fixed: NoMethodError when no issue params are submitted
866 867 * Fixed: projects.atom with required authentication
867 868 * Fixed: External links not correctly displayed in Wiki TOC
868 869 * Fixed: Member role forms in project settings are not hidden after member added
869 870 * Fixed: pre can't be inside p
870 871 * Fixed: session cookie path does not respect RAILS_RELATIVE_URL_ROOT
871 872 * Fixed: mail handler fails when the from address is empty
872 873
873 874
874 875 == 2010-05-01 v0.9.4
875 876
876 877 * Filters collapsed by default on issues index page for a saved query
877 878 * Fixed: When categories list is too big the popup menu doesn't adjust (ex. in the issue list)
878 879 * Fixed: remove "main-menu" div when the menu is empty
879 880 * Fixed: Code syntax highlighting not working in Document page
880 881 * Fixed: Git blame/annotate fails on moved files
881 882 * Fixed: Failing test in test_show_atom
882 883 * Fixed: Migrate from trac - not displayed Wikis
883 884 * Fixed: Email notifications on file upload sent to empty recipient list
884 885 * Fixed: Migrating from trac is not possible, fails to allocate memory
885 886 * Fixed: Lost password no longer flashes a confirmation message
886 887 * Fixed: Crash while deleting in-use enumeration
887 888 * Fixed: Hard coded English string at the selection of issue watchers
888 889 * Fixed: Bazaar v2.1.0 changed behaviour
889 890 * Fixed: Roadmap display can raise an exception if no trackers are selected
890 891 * Fixed: Gravatar breaks layout of "logged in" page
891 892 * Fixed: Reposman.rb on Windows
892 893 * Fixed: Possible error 500 while moving an issue to another project with SQLite
893 894 * Fixed: backslashes in issue description/note should be escaped when quoted
894 895 * Fixed: Long text in <pre> disrupts Associated revisions
895 896 * Fixed: Links to missing wiki pages not red on project overview page
896 897 * Fixed: Cannot delete a project with subprojects that shares versions
897 898 * Fixed: Update of Subversion changesets broken under Solaris
898 899 * Fixed: "Move issues" permission not working for Non member
899 900 * Fixed: Sidebar overlap on Users tab of Group editor
900 901 * Fixed: Error on db:migrate with table prefix set (hardcoded name in principal.rb)
901 902 * Fixed: Report shows sub-projects for non-members
902 903 * Fixed: 500 internal error when browsing any Redmine page in epiphany
903 904 * Fixed: Watchers selection lost when issue creation fails
904 905 * Fixed: When copying projects, redmine should not generate an email to people who created issues
905 906 * Fixed: Issue "#" table cells should have a class attribute to enable fine-grained CSS theme
906 907 * Fixed: Plugin generators should display help if no parameter is given
907 908
908 909
909 910 == 2010-02-28 v0.9.3
910 911
911 912 * Adds filter for system shared versions on the cross project issue list
912 913 * Makes project identifiers searchable
913 914 * Remove invalid utf8 sequences from commit comments and author name
914 915 * Fixed: Wrong link when "http" not included in project "Homepage" link
915 916 * Fixed: Escaping in html email templates
916 917 * Fixed: Pound (#) followed by number with leading zero (0) removes leading zero when rendered in wiki
917 918 * Fixed: Deselecting textile text formatting causes interning empty string errors
918 919 * Fixed: error with postgres when entering a non-numeric id for an issue relation
919 920 * Fixed: div.task incorrectly wrapping on Gantt Chart
920 921 * Fixed: Project copy loses wiki pages hierarchy
921 922 * Fixed: parent project field doesn't include blank value when a member with 'add subproject' permission edits a child project
922 923 * Fixed: Repository.fetch_changesets tries to fetch changesets for archived projects
923 924 * Fixed: Duplicated project name for subproject version on gantt chart
924 925 * Fixed: roadmap shows subprojects issues even if subprojects is unchecked
925 926 * Fixed: IndexError if all the :last menu items are deleted from a menu
926 927 * Fixed: Very high CPU usage for a long time when fetching commits from a large Git repository
927 928
928 929
929 930 == 2010-02-07 v0.9.2
930 931
931 932 * Fixed: Sub-project repository commits not displayed on parent project issues
932 933 * Fixed: Potential security leak on my page calendar
933 934 * Fixed: Project tree structure is broken by deleting the project with the subproject
934 935 * Fixed: Error message shown duplicated when creating a new group
935 936 * Fixed: Firefox cuts off large pages
936 937 * Fixed: Invalid format parameter returns a DoubleRenderError on issues index
937 938 * Fixed: Unnecessary Quote button on locked forum message
938 939 * Fixed: Error raised when trying to view the gantt or calendar with a grouped query
939 940 * Fixed: PDF support for Korean locale
940 941 * Fixed: Deprecation warning in extra/svn/reposman.rb
941 942
942 943
943 944 == 2010-01-30 v0.9.1
944 945
945 946 * Vertical alignment for inline images in formatted text set to 'middle'
946 947 * Fixed: Redmine.pm error "closing dbh with active statement handles at /usr/lib/perl5/Apache/Redmine.pm"
947 948 * Fixed: copyright year in footer set to 2010
948 949 * Fixed: Trac migration script may not output query lines
949 950 * Fixed: Email notifications may affect language of notice messages on the UI
950 951 * Fixed: Can not search for 2 letters word
951 952 * Fixed: Attachments get saved on issue update even if validation fails
952 953 * Fixed: Tab's 'border-bottom' not absent when selected
953 954 * Fixed: Issue summary tables that list by user are not sorted
954 955 * Fixed: Issue pdf export fails if target version is set
955 956 * Fixed: Issue list export to PDF breaks when issues are sorted by a custom field
956 957 * Fixed: SQL error when adding a group
957 958 * Fixes: Min password length during password reset always displays as 4 chars
958 959
959 960
960 961 == 2010-01-09 v0.9.0 (Release candidate)
961 962
962 963 * Unlimited subproject nesting
963 964 * Multiple roles per user per project
964 965 * User groups
965 966 * Inheritence of versions
966 967 * OpenID login
967 968 * "Watched by me" issue filter
968 969 * Project copy
969 970 * Project creation by non admin users
970 971 * Accept emails from anyone on a private project
971 972 * Add email notification on Wiki changes
972 973 * Make issue description non-required field
973 974 * Custom fields for Versions
974 975 * Being able to sort the issue list by custom fields
975 976 * Ability to close versions
976 977 * User display/editing of custom fields attached to their user profile
977 978 * Add "follows" issue relation
978 979 * Copy workflows between trackers and roles
979 980 * Defaults enabled modules list for project creation
980 981 * Weighted version completion percentage on the roadmap
981 982 * Autocreate user account when user submits email that creates new issue
982 983 * CSS class on overdue issues on the issue list
983 984 * Enable tracker update on issue edit form
984 985 * Remove issue watchers
985 986 * Ability to move threads between project forums
986 987 * Changed custom field "Possible values" to a textarea
987 988 * Adds projects association on tracker form
988 989 * Set session store to cookie store by default
989 990 * Set a default wiki page on project creation
990 991 * Roadmap for main project should see Roadmaps for sub projects
991 992 * Ticket grouping on the issue list
992 993 * Hierarchical Project links in the page header
993 994 * Allow My Page blocks to be added to from a plugin
994 995 * Sort issues by multiple columns
995 996 * Filters of saved query are now visible and be adjusted without editing the query
996 997 * Saving "sort order" in custom queries
997 998 * Url to fetch changesets for a repository
998 999 * Managers able to create subprojects
999 1000 * Issue Totals on My Page Modules
1000 1001 * Convert Enumerations to single table inheritance (STI)
1001 1002 * Allow custom my_page blocks to define drop-down names
1002 1003 * "View Issues" user permission added
1003 1004 * Ask user what to do with child pages when deleting a parent wiki page
1004 1005 * Contextual quick search
1005 1006 * Allow resending of password by email
1006 1007 * Change reply subject to be a link to the reply itself
1007 1008 * Include Logged Time as part of the project's Activity history
1008 1009 * REST API for authentication
1009 1010 * Browse through Git branches
1010 1011 * Setup Object Daddy to replace test fixtures
1011 1012 * Setup shoulda to make it easier to test
1012 1013 * Custom fields and overrides on Enumerations
1013 1014 * Add or remove columns from the issue list
1014 1015 * Ability to add new version from issues screen
1015 1016 * Setting to choose which day calendars start
1016 1017 * Asynchronous email delivery method
1017 1018 * RESTful URLs for (almost) everything
1018 1019 * Include issue status in search results and activity pages
1019 1020 * Add email to admin user search filter
1020 1021 * Proper content type for plain text mails
1021 1022 * Default value of project jump box
1022 1023 * Tree based menus
1023 1024 * Ability to use issue status to update percent done
1024 1025 * Second set of issue "Action Links" at the bottom of an issue page
1025 1026 * Proper exist status code for rdm-mailhandler.rb
1026 1027 * Remove incoming email body via a delimiter
1027 1028 * Fixed: Custom querry 'Export to PDF' ignores field selection
1028 1029 * Fixed: Related e-mail notifications aren't threaded
1029 1030 * Fixed: No warning when the creation of a categories from the issue form fails
1030 1031 * Fixed: Actually block issues from closing when relation 'blocked by' isn't closed
1031 1032 * Fixed: Include both first and last name when sorting by users
1032 1033 * Fixed: Table cell with multiple line text
1033 1034 * Fixed: Project overview page shows disabled trackers
1034 1035 * Fixed: Cross project issue relations and user permissions
1035 1036 * Fixed: My page shows tickets the user doesn't have access to
1036 1037 * Fixed: TOC does not parse wiki page reference links with description
1037 1038 * Fixed: Target version-list on bulk edit form is incorrectly sorted
1038 1039 * Fixed: Cannot modify/delete project named "Documents"
1039 1040 * Fixed: Email address in brackets breaks html
1040 1041 * Fixed: Timelog detail loose issue filter passing to report tab
1041 1042 * Fixed: Inform about custom field's name maximum length
1042 1043 * Fixed: Activity page and Atom feed links contain project id instead of identifier
1043 1044 * Fixed: no Atom key for forums with only 1 forum
1044 1045 * Fixed: When reading RSS feed in MS Outlook, the inline links are broken.
1045 1046 * Fixed: Sometimes new posts don't show up in the topic list of a forum.
1046 1047 * Fixed: The all/active filter selection in the project view does not stick.
1047 1048 * Fixed: Login box has Different width
1048 1049 * Fixed: User removed from project - still getting project update emails
1049 1050 * Fixed: Project with the identifier of 'new' cannot be viewed
1050 1051 * Fixed: Artefacts in search view (Cyrillic)
1051 1052 * Fixed: Allow [#id] as subject to reply by email
1052 1053 * Fixed: Wrong language used when closing an issue via a commit message
1053 1054 * Fixed: email handler drops emails for new issues with no subject
1054 1055 * Fixed: Calendar misspelled under Roles/Permissions
1055 1056 * Fixed: Emails from no-reply redmine's address hell cycle
1056 1057 * Fixed: child_pages macro fails on wiki page history
1057 1058 * Fixed: Pre-filled time tracking date ignores timezone
1058 1059 * Fixed: Links on locked users lead to 404 page
1059 1060 * Fixed: Page changes in issue-list when using context menu
1060 1061 * Fixed: diff parser removes lines starting with multiple dashes
1061 1062 * Fixed: Quoting in forums resets message subject
1062 1063 * Fixed: Editing issue comment removes quote link
1063 1064 * Fixed: Redmine.pm ignore browse_repository permission
1064 1065 * Fixed: text formatting breaks on [msg1][msg2]
1065 1066 * Fixed: Spent Time Default Value of 0.0
1066 1067 * Fixed: Wiki pages in search results are referenced by project number, not by project identifier.
1067 1068 * Fixed: When logging in via an autologin cookie the user's last_login_on should be updated
1068 1069 * Fixed: 50k users cause problems in project->settings->members screen
1069 1070 * Fixed: Document timestamp needs to show updated timestamps
1070 1071 * Fixed: Users getting notifications for issues they are no longer allowed to view
1071 1072 * Fixed: issue summary counts should link to the issue list without subprojects
1072 1073 * Fixed: 'Delete' link on LDAP list has no effect
1073 1074
1074 1075
1075 1076 == 2009-11-15 v0.8.7
1076 1077
1077 1078 * Fixed: Hide paragraph terminator at the end of headings on html export
1078 1079 * Fixed: pre tags containing "<pre*"
1079 1080 * Fixed: First date of the date range not included in the time report with SQLite
1080 1081 * Fixed: Password field not styled correctly on alternative stylesheet
1081 1082 * Fixed: Error when sumbitting a POST request that requires a login
1082 1083 * Fixed: CSRF vulnerabilities
1083 1084
1084 1085
1085 1086 == 2009-11-04 v0.8.6
1086 1087
1087 1088 * Change links to closed issues to be a grey color
1088 1089 * Change subversion adapter to not cache authentication and run non interactively
1089 1090 * Fixed: Custom Values with a nil value cause HTTP error 500
1090 1091 * Fixed: Failure to convert HTML entities when editing an Issue reply
1091 1092 * Fixed: Error trying to show repository when there are no comments in a changeset
1092 1093 * Fixed: account/show/:user_id should not be accessible for other users not in your projects
1093 1094 * Fixed: XSS vulnerabilities
1094 1095 * Fixed: IssuesController#destroy should accept POST only
1095 1096 * Fixed: Inline images in wiki headings
1096 1097
1097 1098
1098 1099 == 2009-09-13 v0.8.5
1099 1100
1100 1101 * Incoming mail handler : Allow spaces between keywords and colon
1101 1102 * Do not require a non-word character after a comma in Redmine links
1102 1103 * Include issue hyperlinks in reminder emails
1103 1104 * Prevent nil error when retrieving svn version
1104 1105 * Various plugin hooks added
1105 1106 * Add plugins information to script/about
1106 1107 * Fixed: 500 Internal Server Error is raised if add an empty comment to the news
1107 1108 * Fixed: Atom links for wiki pages are not correct
1108 1109 * Fixed: Atom feeds leak email address
1109 1110 * Fixed: Case sensitivity in Issue filtering
1110 1111 * Fixed: When reading RSS feed, the inline-embedded images are not properly shown
1111 1112
1112 1113
1113 1114 == 2009-05-17 v0.8.4
1114 1115
1115 1116 * Allow textile mailto links
1116 1117 * Fixed: memory consumption when uploading file
1117 1118 * Fixed: Mercurial integration doesn't work if Redmine is installed in folder path containing space
1118 1119 * Fixed: an error is raised when no tab is available on project settings
1119 1120 * Fixed: insert image macro corrupts urls with excalamation marks
1120 1121 * Fixed: error on cross-project gantt PNG export
1121 1122 * Fixed: self and alternate links in atom feeds do not respect Atom specs
1122 1123 * Fixed: accept any svn tunnel scheme in repository URL
1123 1124 * Fixed: issues/show should accept user's rss key
1124 1125 * Fixed: consistency of custom fields display on the issue detail view
1125 1126 * Fixed: wiki comments length validation is missing
1126 1127 * Fixed: weak autologin token generation algorithm causes duplicate tokens
1127 1128
1128 1129
1129 1130 == 2009-04-05 v0.8.3
1130 1131
1131 1132 * Separate project field and subject in cross-project issue view
1132 1133 * Ability to set language for redmine:load_default_data task using REDMINE_LANG environment variable
1133 1134 * Rescue Redmine::DefaultData::DataAlreadyLoaded in redmine:load_default_data task
1134 1135 * CSS classes to highlight own and assigned issues
1135 1136 * Hide "New file" link on wiki pages from printing
1136 1137 * Flush buffer when asking for language in redmine:load_default_data task
1137 1138 * Minimum project identifier length set to 1
1138 1139 * Include headers so that emails don't trigger vacation auto-responders
1139 1140 * Fixed: Time entries csv export links for all projects are malformed
1140 1141 * Fixed: Files without Version aren't visible in the Activity page
1141 1142 * Fixed: Commit logs are centered in the repo browser
1142 1143 * Fixed: News summary field content is not searchable
1143 1144 * Fixed: Journal#save has a wrong signature
1144 1145 * Fixed: Email footer signature convention
1145 1146 * Fixed: Timelog report do not show time for non-versioned issues
1146 1147
1147 1148
1148 1149 == 2009-03-07 v0.8.2
1149 1150
1150 1151 * Send an email to the user when an administrator activates a registered user
1151 1152 * Strip keywords from received email body
1152 1153 * Footer updated to 2009
1153 1154 * Show RSS-link even when no issues is found
1154 1155 * One click filter action in activity view
1155 1156 * Clickable/linkable line #'s while browsing the repo or viewing a file
1156 1157 * Links to versions on files list
1157 1158 * Added request and controller objects to the hooks by default
1158 1159 * Fixed: exporting an issue with attachments to PDF raises an error
1159 1160 * Fixed: "too few arguments" error may occur on activerecord error translation
1160 1161 * Fixed: "Default columns Displayed on the Issues list" setting is not easy to read
1161 1162 * Fixed: visited links to closed tickets are not striked through with IE6
1162 1163 * Fixed: MailHandler#plain_text_body returns nil if there was nothing to strip
1163 1164 * Fixed: MailHandler raises an error when processing an email without From header
1164 1165
1165 1166
1166 1167 == 2009-02-15 v0.8.1
1167 1168
1168 1169 * Select watchers on new issue form
1169 1170 * Issue description is no longer a required field
1170 1171 * Files module: ability to add files without version
1171 1172 * Jump to the current tab when using the project quick-jump combo
1172 1173 * Display a warning if some attachments were not saved
1173 1174 * Import custom fields values from emails on issue creation
1174 1175 * Show view/annotate/download links on entry and annotate views
1175 1176 * Admin Info Screen: Display if plugin assets directory is writable
1176 1177 * Adds a 'Create and continue' button on the new issue form
1177 1178 * IMAP: add options to move received emails
1178 1179 * Do not show Category field when categories are not defined
1179 1180 * Lower the project identifier limit to a minimum of two characters
1180 1181 * Add "closed" html class to closed entries in issue list
1181 1182 * Fixed: broken redirect URL on login failure
1182 1183 * Fixed: Deleted files are shown when using Darcs
1183 1184 * Fixed: Darcs adapter works on Win32 only
1184 1185 * Fixed: syntax highlight doesn't appear in new ticket preview
1185 1186 * Fixed: email notification for changes I make still occurs when running Repository.fetch_changesets
1186 1187 * Fixed: no error is raised when entering invalid hours on the issue update form
1187 1188 * Fixed: Details time log report CSV export doesn't honour date format from settings
1188 1189 * Fixed: invalid css classes on issue details
1189 1190 * Fixed: Trac importer creates duplicate custom values
1190 1191 * Fixed: inline attached image should not match partial filename
1191 1192
1192 1193
1193 1194 == 2008-12-30 v0.8.0
1194 1195
1195 1196 * Setting added in order to limit the number of diff lines that should be displayed
1196 1197 * Makes logged-in username in topbar linking to
1197 1198 * Mail handler: strip tags when receiving a html-only email
1198 1199 * Mail handler: add watchers before sending notification
1199 1200 * Adds a css class (overdue) to overdue issues on issue lists and detail views
1200 1201 * Fixed: project activity truncated after viewing user's activity
1201 1202 * Fixed: email address entered for password recovery shouldn't be case-sensitive
1202 1203 * Fixed: default flag removed when editing a default enumeration
1203 1204 * Fixed: default category ignored when adding a document
1204 1205 * Fixed: error on repository user mapping when a repository username is blank
1205 1206 * Fixed: Firefox cuts off large diffs
1206 1207 * Fixed: CVS browser should not show dead revisions (deleted files)
1207 1208 * Fixed: escape double-quotes in image titles
1208 1209 * Fixed: escape textarea content when editing a issue note
1209 1210 * Fixed: JS error on context menu with IE
1210 1211 * Fixed: bold syntax around single character in series doesn't work
1211 1212 * Fixed several XSS vulnerabilities
1212 1213 * Fixed a SQL injection vulnerability
1213 1214
1214 1215
1215 1216 == 2008-12-07 v0.8.0-rc1
1216 1217
1217 1218 * Wiki page protection
1218 1219 * Wiki page hierarchy. Parent page can be assigned on the Rename screen
1219 1220 * Adds support for issue creation via email
1220 1221 * Adds support for free ticket filtering and custom queries on Gantt chart and calendar
1221 1222 * Cross-project search
1222 1223 * Ability to search a project and its subprojects
1223 1224 * Ability to search the projects the user belongs to
1224 1225 * Adds custom fields on time entries
1225 1226 * Adds boolean and list custom fields for time entries as criteria on time report
1226 1227 * Cross-project time reports
1227 1228 * Display latest user's activity on account/show view
1228 1229 * Show last connexion time on user's page
1229 1230 * Obfuscates email address on user's account page using javascript
1230 1231 * wiki TOC rendered as an unordered list
1231 1232 * Adds the ability to search for a user on the administration users list
1232 1233 * Adds the ability to search for a project name or identifier on the administration projects list
1233 1234 * Redirect user to the previous page after logging in
1234 1235 * Adds a permission 'view wiki edits' so that wiki history can be hidden to certain users
1235 1236 * Adds permissions for viewing the watcher list and adding new watchers on the issue detail view
1236 1237 * Adds permissions to let users edit and/or delete their messages
1237 1238 * Link to activity view when displaying dates
1238 1239 * Hide Redmine version in atom feeds and pdf properties
1239 1240 * Maps repository users to Redmine users. Users with same username or email are automatically mapped. Mapping can be manually adjusted in repository settings. Multiple usernames can be mapped to the same Redmine user.
1240 1241 * Sort users by their display names so that user dropdown lists are sorted alphabetically
1241 1242 * Adds estimated hours to issue filters
1242 1243 * Switch order of current and previous revisions in side-by-side diff
1243 1244 * Render the commit changes list as a tree
1244 1245 * Adds watch/unwatch functionality at forum topic level
1245 1246 * When moving an issue to another project, reassign it to the category with same name if any
1246 1247 * Adds child_pages macro for wiki pages
1247 1248 * Use GET instead of POST on roadmap (#718), gantt and calendar forms
1248 1249 * Search engine: display total results count and count by result type
1249 1250 * Email delivery configuration moved to an unversioned YAML file (config/email.yml, see the sample file)
1250 1251 * Adds icons on search results
1251 1252 * Adds 'Edit' link on account/show for admin users
1252 1253 * Adds Lock/Unlock/Activate link on user edit screen
1253 1254 * Adds user count in status drop down on admin user list
1254 1255 * Adds multi-levels blockquotes support by using > at the beginning of lines
1255 1256 * Adds a Reply link to each issue note
1256 1257 * Adds plain text only option for mail notifications
1257 1258 * Gravatar support for issue detail, user grid, and activity stream (disabled by default)
1258 1259 * Adds 'Delete wiki pages attachments' permission
1259 1260 * Show the most recent file when displaying an inline image
1260 1261 * Makes permission screens localized
1261 1262 * AuthSource list: display associated users count and disable 'Delete' buton if any
1262 1263 * Make the 'duplicates of' relation asymmetric
1263 1264 * Adds username to the password reminder email
1264 1265 * Adds links to forum messages using message#id syntax
1265 1266 * Allow same name for custom fields on different object types
1266 1267 * One-click bulk edition using the issue list context menu within the same project
1267 1268 * Adds support for commit logs reencoding to UTF-8 before insertion in the database. Source encoding of commit logs can be selected in Application settings -> Repositories.
1268 1269 * Adds checkboxes toggle links on permissions report
1269 1270 * Adds Trac-Like anchors on wiki headings
1270 1271 * Adds support for wiki links with anchor
1271 1272 * Adds category to the issue context menu
1272 1273 * Adds a workflow overview screen
1273 1274 * Appends the filename to the attachment url so that clients that ignore content-disposition http header get the real filename
1274 1275 * Dots allowed in custom field name
1275 1276 * Adds posts quoting functionality
1276 1277 * Adds an option to generate sequential project identifiers
1277 1278 * Adds mailto link on the user administration list
1278 1279 * Ability to remove enumerations (activities, priorities, document categories) that are in use. Associated objects can be reassigned to another value
1279 1280 * Gantt chart: display issues that don't have a due date if they are assigned to a version with a date
1280 1281 * Change projects homepage limit to 255 chars
1281 1282 * Improved on-the-fly account creation. If some attributes are missing (eg. not present in the LDAP) or are invalid, the registration form is displayed so that the user is able to fill or fix these attributes
1282 1283 * Adds "please select" to activity select box if no activity is set as default
1283 1284 * Do not silently ignore timelog validation failure on issue edit
1284 1285 * Adds a rake task to send reminder emails
1285 1286 * Allow empty cells in wiki tables
1286 1287 * Makes wiki text formatter pluggable
1287 1288 * Adds back textile acronyms support
1288 1289 * Remove pre tag attributes
1289 1290 * Plugin hooks
1290 1291 * Pluggable admin menu
1291 1292 * Plugins can provide activity content
1292 1293 * Moves plugin list to its own administration menu item
1293 1294 * Adds url and author_url plugin attributes
1294 1295 * Adds Plugin#requires_redmine method so that plugin compatibility can be checked against current Redmine version
1295 1296 * Adds atom feed on time entries details
1296 1297 * Adds project name to issues feed title
1297 1298 * Adds a css class on menu items in order to apply item specific styles (eg. icons)
1298 1299 * Adds a Redmine plugin generators
1299 1300 * Adds timelog link to the issue context menu
1300 1301 * Adds links to the user page on various views
1301 1302 * Turkish translation by Ismail Sezen
1302 1303 * Catalan translation
1303 1304 * Vietnamese translation
1304 1305 * Slovak translation
1305 1306 * Better naming of activity feed if only one kind of event is displayed
1306 1307 * Enable syntax highlight on issues, messages and news
1307 1308 * Add target version to the issue list context menu
1308 1309 * Hide 'Target version' filter if no version is defined
1309 1310 * Add filters on cross-project issue list for custom fields marked as 'For all projects'
1310 1311 * Turn ftp urls into links
1311 1312 * Hiding the View Differences button when a wiki page's history only has one version
1312 1313 * Messages on a Board can now be sorted by the number of replies
1313 1314 * Adds a class ('me') to events of the activity view created by current user
1314 1315 * Strip pre/code tags content from activity view events
1315 1316 * Display issue notes in the activity view
1316 1317 * Adds links to changesets atom feed on repository browser
1317 1318 * Track project and tracker changes in issue history
1318 1319 * Adds anchor to atom feed messages links
1319 1320 * Adds a key in lang files to set the decimal separator (point or comma) in csv exports
1320 1321 * Makes importer work with Trac 0.8.x
1321 1322 * Upgraded to Prototype 1.6.0.1
1322 1323 * File viewer for attached text files
1323 1324 * Menu mapper: add support for :before, :after and :last options to #push method and add #delete method
1324 1325 * Removed inconsistent revision numbers on diff view
1325 1326 * CVS: add support for modules names with spaces
1326 1327 * Log the user in after registration if account activation is not needed
1327 1328 * Mercurial adapter improvements
1328 1329 * Trac importer: read session_attribute table to find user's email and real name
1329 1330 * Ability to disable unused SCM adapters in application settings
1330 1331 * Adds Filesystem adapter
1331 1332 * Clear changesets and changes with raw sql when deleting a repository for performance
1332 1333 * Redmine.pm now uses the 'commit access' permission defined in Redmine
1333 1334 * Reposman can create any type of scm (--scm option)
1334 1335 * Reposman creates a repository if the 'repository' module is enabled at project level only
1335 1336 * Display svn properties in the browser, svn >= 1.5.0 only
1336 1337 * Reduces memory usage when importing large git repositories
1337 1338 * Wider SVG graphs in repository stats
1338 1339 * SubversionAdapter#entries performance improvement
1339 1340 * SCM browser: ability to download raw unified diffs
1340 1341 * More detailed error message in log when scm command fails
1341 1342 * Adds support for file viewing with Darcs 2.0+
1342 1343 * Check that git changeset is not in the database before creating it
1343 1344 * Unified diff viewer for attached files with .patch or .diff extension
1344 1345 * File size display with Bazaar repositories
1345 1346 * Git adapter: use commit time instead of author time
1346 1347 * Prettier url for changesets
1347 1348 * Makes changes link to entries on the revision view
1348 1349 * Adds a field on the repository view to browse at specific revision
1349 1350 * Adds new projects atom feed
1350 1351 * Added rake tasks to generate rcov code coverage reports
1351 1352 * Add Redcloth's :block_markdown_rule to allow horizontal rules in wiki
1352 1353 * Show the project hierarchy in the drop down list for new membership on user administration screen
1353 1354 * Split user edit screen into tabs
1354 1355 * Renames bundled RedCloth to RedCloth3 to avoid RedCloth 4 to be loaded instead
1355 1356 * Fixed: Roadmap crashes when a version has a due date > 2037
1356 1357 * Fixed: invalid effective date (eg. 99999-01-01) causes an error on version edition screen
1357 1358 * Fixed: login filter providing incorrect back_url for Redmine installed in sub-directory
1358 1359 * Fixed: logtime entry duplicated when edited from parent project
1359 1360 * Fixed: wrong digest for text files under Windows
1360 1361 * Fixed: associated revisions are displayed in wrong order on issue view
1361 1362 * Fixed: Git Adapter date parsing ignores timezone
1362 1363 * Fixed: Printing long roadmap doesn't split across pages
1363 1364 * Fixes custom fields display order at several places
1364 1365 * Fixed: urls containing @ are parsed as email adress by the wiki formatter
1365 1366 * Fixed date filters accuracy with SQLite
1366 1367 * Fixed: tokens not escaped in highlight_tokens regexp
1367 1368 * Fixed Bazaar shared repository browsing
1368 1369 * Fixes platform determination under JRuby
1369 1370 * Fixed: Estimated time in issue's journal should be rounded to two decimals
1370 1371 * Fixed: 'search titles only' box ignored after one search is done on titles only
1371 1372 * Fixed: non-ASCII subversion path can't be displayed
1372 1373 * Fixed: Inline images don't work if file name has upper case letters or if image is in BMP format
1373 1374 * Fixed: document listing shows on "my page" when viewing documents is disabled for the role
1374 1375 * Fixed: Latest news appear on the homepage for projects with the News module disabled
1375 1376 * Fixed: cross-project issue list should not show issues of projects for which the issue tracking module was disabled
1376 1377 * Fixed: the default status is lost when reordering issue statuses
1377 1378 * Fixes error with Postgresql and non-UTF8 commit logs
1378 1379 * Fixed: textile footnotes no longer work
1379 1380 * Fixed: http links containing parentheses fail to reder correctly
1380 1381 * Fixed: GitAdapter#get_rev should use current branch instead of hardwiring master
1381 1382
1382 1383
1383 1384 == 2008-07-06 v0.7.3
1384 1385
1385 1386 * Allow dot in firstnames and lastnames
1386 1387 * Add project name to cross-project Atom feeds
1387 1388 * Encoding set to utf8 in example database.yml
1388 1389 * HTML titles on forums related views
1389 1390 * Fixed: various XSS vulnerabilities
1390 1391 * Fixed: Entourage (and some old client) fails to correctly render notification styles
1391 1392 * Fixed: Fixed: timelog redirects inappropriately when :back_url is blank
1392 1393 * Fixed: wrong relative paths to images in wiki_syntax.html
1393 1394
1394 1395
1395 1396 == 2008-06-15 v0.7.2
1396 1397
1397 1398 * "New Project" link on Projects page
1398 1399 * Links to repository directories on the repo browser
1399 1400 * Move status to front in Activity View
1400 1401 * Remove edit step from Status context menu
1401 1402 * Fixed: No way to do textile horizontal rule
1402 1403 * Fixed: Repository: View differences doesn't work
1403 1404 * Fixed: attachement's name maybe invalid.
1404 1405 * Fixed: Error when creating a new issue
1405 1406 * Fixed: NoMethodError on @available_filters.has_key?
1406 1407 * Fixed: Check All / Uncheck All in Email Settings
1407 1408 * Fixed: "View differences" of one file at /repositories/revision/ fails
1408 1409 * Fixed: Column width in "my page"
1409 1410 * Fixed: private subprojects are listed on Issues view
1410 1411 * Fixed: Textile: bold, italics, underline, etc... not working after parentheses
1411 1412 * Fixed: Update issue form: comment field from log time end out of screen
1412 1413 * Fixed: Editing role: "issue can be assigned to this role" out of box
1413 1414 * Fixed: Unable use angular braces after include word
1414 1415 * Fixed: Using '*' as keyword for repository referencing keywords doesn't work
1415 1416 * Fixed: Subversion repository "View differences" on each file rise ERROR
1416 1417 * Fixed: View differences for individual file of a changeset fails if the repository URL doesn't point to the repository root
1417 1418 * Fixed: It is possible to lock out the last admin account
1418 1419 * Fixed: Wikis are viewable for anonymous users on public projects, despite not granting access
1419 1420 * Fixed: Issue number display clipped on 'my issues'
1420 1421 * Fixed: Roadmap version list links not carrying state
1421 1422 * Fixed: Log Time fieldset in IssueController#edit doesn't set default Activity as default
1422 1423 * Fixed: git's "get_rev" API should use repo's current branch instead of hardwiring "master"
1423 1424 * Fixed: browser's language subcodes ignored
1424 1425 * Fixed: Error on project selection with numeric (only) identifier.
1425 1426 * Fixed: Link to PDF doesn't work after creating new issue
1426 1427 * Fixed: "Replies" should not be shown on forum threads that are locked
1427 1428 * Fixed: SVN errors lead to svn username/password being displayed to end users (security issue)
1428 1429 * Fixed: http links containing hashes don't display correct
1429 1430 * Fixed: Allow ampersands in Enumeration names
1430 1431 * Fixed: Atom link on saved query does not include query_id
1431 1432 * Fixed: Logtime info lost when there's an error updating an issue
1432 1433 * Fixed: TOC does not parse colorization markups
1433 1434 * Fixed: CVS: add support for modules names with spaces
1434 1435 * Fixed: Bad rendering on projects/add
1435 1436 * Fixed: exception when viewing differences on cvs
1436 1437 * Fixed: export issue to pdf will messup when use Chinese language
1437 1438 * Fixed: Redmine::Scm::Adapters::GitAdapter#get_rev ignored GIT_BIN constant
1438 1439 * Fixed: Adding non-ASCII new issue type in the New Issue page have encoding error using IE
1439 1440 * Fixed: Importing from trac : some wiki links are messed
1440 1441 * Fixed: Incorrect weekend definition in Hebrew calendar locale
1441 1442 * Fixed: Atom feeds don't provide author section for repository revisions
1442 1443 * Fixed: In Activity views, changesets titles can be multiline while they should not
1443 1444 * Fixed: Ignore unreadable subversion directories (read disabled using authz)
1444 1445 * Fixed: lib/SVG/Graph/Graph.rb can't externalize stylesheets
1445 1446 * Fixed: Close statement handler in Redmine.pm
1446 1447
1447 1448
1448 1449 == 2008-05-04 v0.7.1
1449 1450
1450 1451 * Thai translation added (Gampol Thitinilnithi)
1451 1452 * Translations updates
1452 1453 * Escape HTML comment tags
1453 1454 * Prevent "can't convert nil into String" error when :sort_order param is not present
1454 1455 * Fixed: Updating tickets add a time log with zero hours
1455 1456 * Fixed: private subprojects names are revealed on the project overview
1456 1457 * Fixed: Search for target version of "none" fails with postgres 8.3
1457 1458 * Fixed: Home, Logout, Login links shouldn't be absolute links
1458 1459 * Fixed: 'Latest projects' box on the welcome screen should be hidden if there are no projects
1459 1460 * Fixed: error when using upcase language name in coderay
1460 1461 * Fixed: error on Trac import when :due attribute is nil
1461 1462
1462 1463
1463 1464 == 2008-04-28 v0.7.0
1464 1465
1465 1466 * Forces Redmine to use rails 2.0.2 gem when vendor/rails is not present
1466 1467 * Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list.
1467 1468 * Add predefined date ranges to the time report
1468 1469 * Time report can be done at issue level
1469 1470 * Various timelog report enhancements
1470 1471 * Accept the following formats for "hours" field: 1h, 1 h, 1 hour, 2 hours, 30m, 30min, 1h30, 1h30m, 1:30
1471 1472 * Display the context menu above and/or to the left of the click if needed
1472 1473 * Make the admin project files list sortable
1473 1474 * Mercurial: display working directory files sizes unless browsing a specific revision
1474 1475 * Preserve status filter and page number when using lock/unlock/activate links on the users list
1475 1476 * Redmine.pm support for LDAP authentication
1476 1477 * Better error message and AR errors in log for failed LDAP on-the-fly user creation
1477 1478 * Redirected user to where he is coming from after logging hours
1478 1479 * Warn user that subprojects are also deleted when deleting a project
1479 1480 * Include subprojects versions on calendar and gantt
1480 1481 * Notify project members when a message is posted if they want to receive notifications
1481 1482 * Fixed: Feed content limit setting has no effect
1482 1483 * Fixed: Priorities not ordered when displayed as a filter in issue list
1483 1484 * Fixed: can not display attached images inline in message replies
1484 1485 * Fixed: Boards are not deleted when project is deleted
1485 1486 * Fixed: trying to preview a new issue raises an exception with postgresql
1486 1487 * Fixed: single file 'View difference' links do not work because of duplicate slashes in url
1487 1488 * Fixed: inline image not displayed when including a wiki page
1488 1489 * Fixed: CVS duplicate key violation
1489 1490 * Fixed: ActiveRecord::StaleObjectError exception on closing a set of circular duplicate issues
1490 1491 * Fixed: custom field filters behaviour
1491 1492 * Fixed: Postgresql 8.3 compatibility
1492 1493 * Fixed: Links to repository directories don't work
1493 1494
1494 1495
1495 1496 == 2008-03-29 v0.7.0-rc1
1496 1497
1497 1498 * Overall activity view and feed added, link is available on the project list
1498 1499 * Git VCS support
1499 1500 * Rails 2.0 sessions cookie store compatibility
1500 1501 * Use project identifiers in urls instead of ids
1501 1502 * Default configuration data can now be loaded from the administration screen
1502 1503 * Administration settings screen split to tabs (email notifications options moved to 'Settings')
1503 1504 * Project description is now unlimited and optional
1504 1505 * Wiki annotate view
1505 1506 * Escape HTML tag in textile content
1506 1507 * Add Redmine links to documents, versions, attachments and repository files
1507 1508 * New setting to specify how many objects should be displayed on paginated lists. There are 2 ways to select a set of issues on the issue list:
1508 1509 * by using checkbox and/or the little pencil that will select/unselect all issues
1509 1510 * by clicking on the rows (but not on the links), Ctrl and Shift keys can be used to select multiple issues
1510 1511 * Context menu disabled on links so that the default context menu of the browser is displayed when right-clicking on a link (click anywhere else on the row to display the context menu)
1511 1512 * User display format is now configurable in administration settings
1512 1513 * Issue list now supports bulk edit/move/delete (for a set of issues that belong to the same project)
1513 1514 * Merged 'change status', 'edit issue' and 'add note' actions:
1514 1515 * Users with 'edit issues' permission can now update any property including custom fields when adding a note or changing the status
1515 1516 * 'Change issue status' permission removed. To change an issue status, a user just needs to have either 'Edit' or 'Add note' permissions and some workflow transitions allowed
1516 1517 * Details by assignees on issue summary view
1517 1518 * 'New issue' link in the main menu (accesskey 7). The drop-down lists to add an issue on the project overview and the issue list are removed
1518 1519 * Change status select box default to current status
1519 1520 * Preview for issue notes, news and messages
1520 1521 * Optional description for attachments
1521 1522 * 'Fixed version' label changed to 'Target version'
1522 1523 * Let the user choose when deleting issues with reported hours to:
1523 1524 * delete the hours
1524 1525 * assign the hours to the project
1525 1526 * reassign the hours to another issue
1526 1527 * Date range filter and pagination on time entries detail view
1527 1528 * Propagate time tracking to the parent project
1528 1529 * Switch added on the project activity view to include subprojects
1529 1530 * Display total estimated and spent hours on the version detail view
1530 1531 * Weekly time tracking block for 'My page'
1531 1532 * Permissions to edit time entries
1532 1533 * Include subprojects on the issue list, calendar, gantt and timelog by default (can be turned off is administration settings)
1533 1534 * Roadmap enhancements (separate related issues from wiki contents, leading h1 in version wiki pages is hidden, smaller wiki headings)
1534 1535 * Make versions with same date sorted by name
1535 1536 * Allow issue list to be sorted by target version
1536 1537 * Related changesets messages displayed on the issue details view
1537 1538 * Create a journal and send an email when an issue is closed by commit
1538 1539 * Add 'Author' to the available columns for the issue list
1539 1540 * More appropriate default sort order on sortable columns
1540 1541 * Add issue subject to the time entries view and issue subject, description and tracker to the csv export
1541 1542 * Permissions to edit issue notes
1542 1543 * Display date/time instead of date on files list
1543 1544 * Do not show Roadmap menu item if the project doesn't define any versions
1544 1545 * Allow longer version names (60 chars)
1545 1546 * Ability to copy an existing workflow when creating a new role
1546 1547 * Display custom fields in two columns on the issue form
1547 1548 * Added 'estimated time' in the csv export of the issue list
1548 1549 * Display the last 30 days on the activity view rather than the current month (number of days can be configured in the application settings)
1549 1550 * Setting for whether new projects should be public by default
1550 1551 * User preference to choose how comments/replies are displayed: in chronological or reverse chronological order
1551 1552 * Added default value for custom fields
1552 1553 * Added tabindex property on wiki toolbar buttons (to easily move from field to field using the tab key)
1553 1554 * Redirect to issue page after creating a new issue
1554 1555 * Wiki toolbar improvements (mainly for Firefox)
1555 1556 * Display wiki syntax quick ref link on all wiki textareas
1556 1557 * Display links to Atom feeds
1557 1558 * Breadcrumb nav for the forums
1558 1559 * Show replies when choosing to display messages in the activity
1559 1560 * Added 'include' macro to include another wiki page
1560 1561 * RedmineWikiFormatting page available as a static HTML file locally
1561 1562 * Wrap diff content
1562 1563 * Strip out email address from authors in repository screens
1563 1564 * Highlight the current item of the main menu
1564 1565 * Added simple syntax highlighters for php and java languages
1565 1566 * Do not show empty diffs
1566 1567 * Show explicit error message when the scm command failed (eg. when svn binary is not available)
1567 1568 * Lithuanian translation added (Sergej Jegorov)
1568 1569 * Ukrainan translation added (Natalia Konovka & Mykhaylo Sorochan)
1569 1570 * Danish translation added (Mads Vestergaard)
1570 1571 * Added i18n support to the jstoolbar and various settings screen
1571 1572 * RedCloth's glyphs no longer user
1572 1573 * New icons for the wiki toolbar (from http://www.famfamfam.com/lab/icons/silk/)
1573 1574 * The following menus can now be extended by plugins: top_menu, account_menu, application_menu
1574 1575 * Added a simple rake task to fetch changesets from the repositories: rake redmine:fetch_changesets
1575 1576 * Remove hardcoded "Redmine" strings in account related emails and use application title instead
1576 1577 * Mantis importer preserve bug ids
1577 1578 * Trac importer: Trac guide wiki pages skipped
1578 1579 * Trac importer: wiki attachments migration added
1579 1580 * Trac importer: support database schema for Trac migration
1580 1581 * Trac importer: support CamelCase links
1581 1582 * Removes the Redmine version from the footer (can be viewed on admin -> info)
1582 1583 * Rescue and display an error message when trying to delete a role that is in use
1583 1584 * Add various 'X-Redmine' headers to email notifications: X-Redmine-Host, X-Redmine-Site, X-Redmine-Project, X-Redmine-Issue-Id, -Author, -Assignee, X-Redmine-Topic-Id
1584 1585 * Add "--encoding utf8" option to the Mercurial "hg log" command in order to get utf8 encoded commit logs
1585 1586 * Fixed: Gantt and calendar not properly refreshed (fragment caching removed)
1586 1587 * Fixed: Textile image with style attribute cause internal server error
1587 1588 * Fixed: wiki TOC not rendered properly when used in an issue or document description
1588 1589 * Fixed: 'has already been taken' error message on username and email fields if left empty
1589 1590 * Fixed: non-ascii attachement filename with IE
1590 1591 * Fixed: wrong url for wiki syntax pop-up when Redmine urls are prefixed
1591 1592 * Fixed: search for all words doesn't work
1592 1593 * Fixed: Do not show sticky and locked checkboxes when replying to a message
1593 1594 * Fixed: Mantis importer: do not duplicate Mantis username in firstname and lastname if realname is blank
1594 1595 * Fixed: Date custom fields not displayed as specified in application settings
1595 1596 * Fixed: titles not escaped in the activity view
1596 1597 * Fixed: issue queries can not use custom fields marked as 'for all projects' in a project context
1597 1598 * Fixed: on calendar, gantt and in the tracker filter on the issue list, only active trackers of the project (and its sub projects) should be available
1598 1599 * Fixed: locked users should not receive email notifications
1599 1600 * Fixed: custom field selection is not saved when unchecking them all on project settings
1600 1601 * Fixed: can not lock a topic when creating it
1601 1602 * Fixed: Incorrect filtering for unset values when using 'is not' filter
1602 1603 * Fixed: PostgreSQL issues_seq_id not updated when using Trac importer
1603 1604 * Fixed: ajax pagination does not scroll up
1604 1605 * Fixed: error when uploading a file with no content-type specified by the browser
1605 1606 * Fixed: wiki and changeset links not displayed when previewing issue description or notes
1606 1607 * Fixed: 'LdapError: no bind result' error when authenticating
1607 1608 * Fixed: 'LdapError: invalid binding information' when no username/password are set on the LDAP account
1608 1609 * Fixed: CVS repository doesn't work if port is used in the url
1609 1610 * Fixed: Email notifications: host name is missing in generated links
1610 1611 * Fixed: Email notifications: referenced changesets, wiki pages, attachments... are not turned into links
1611 1612 * Fixed: Do not clear issue relations when moving an issue to another project if cross-project issue relations are allowed
1612 1613 * Fixed: "undefined method 'textilizable'" error on email notification when running Repository#fetch_changesets from the console
1613 1614 * Fixed: Do not send an email with no recipient, cc or bcc
1614 1615 * Fixed: fetch_changesets fails on commit comments that close 2 duplicates issues.
1615 1616 * Fixed: Mercurial browsing under unix-like os and for directory depth > 2
1616 1617 * Fixed: Wiki links with pipe can not be used in wiki tables
1617 1618 * Fixed: migrate_from_trac doesn't import timestamps of wiki and tickets
1618 1619 * Fixed: when bulk editing, setting "Assigned to" to "nobody" causes an sql error with Postgresql
1619 1620
1620 1621
1621 1622 == 2008-03-12 v0.6.4
1622 1623
1623 1624 * Fixed: private projects name are displayed on account/show even if the current user doesn't have access to these private projects
1624 1625 * Fixed: potential LDAP authentication security flaw
1625 1626 * Fixed: context submenus on the issue list don't show up with IE6.
1626 1627 * Fixed: Themes are not applied with Rails 2.0
1627 1628 * Fixed: crash when fetching Mercurial changesets if changeset[:files] is nil
1628 1629 * Fixed: Mercurial repository browsing
1629 1630 * Fixed: undefined local variable or method 'log' in CvsAdapter when a cvs command fails
1630 1631 * Fixed: not null constraints not removed with Postgresql
1631 1632 * Doctype set to transitional
1632 1633
1633 1634
1634 1635 == 2007-12-18 v0.6.3
1635 1636
1636 1637 * Fixed: upload doesn't work in 'Files' section
1637 1638
1638 1639
1639 1640 == 2007-12-16 v0.6.2
1640 1641
1641 1642 * Search engine: issue custom fields can now be searched
1642 1643 * News comments are now textilized
1643 1644 * Updated Japanese translation (Satoru Kurashiki)
1644 1645 * Updated Chinese translation (Shortie Lo)
1645 1646 * Fixed Rails 2.0 compatibility bugs:
1646 1647 * Unable to create a wiki
1647 1648 * Gantt and calendar error
1648 1649 * Trac importer error (readonly? is defined by ActiveRecord)
1649 1650 * Fixed: 'assigned to me' filter broken
1650 1651 * Fixed: crash when validation fails on issue edition with no custom fields
1651 1652 * Fixed: reposman "can't find group" error
1652 1653 * Fixed: 'LDAP account password is too long' error when leaving the field empty on creation
1653 1654 * Fixed: empty lines when displaying repository files with Windows style eol
1654 1655 * Fixed: missing body closing tag in repository annotate and entry views
1655 1656
1656 1657
1657 1658 == 2007-12-10 v0.6.1
1658 1659
1659 1660 * Rails 2.0 compatibility
1660 1661 * Custom fields can now be displayed as columns on the issue list
1661 1662 * Added version details view (accessible from the roadmap)
1662 1663 * Roadmap: more accurate completion percentage calculation (done ratio of open issues is now taken into account)
1663 1664 * Added per-project tracker selection. Trackers can be selected on project settings
1664 1665 * Anonymous users can now be allowed to create, edit, comment issues, comment news and post messages in the forums
1665 1666 * Forums: messages can now be edited/deleted (explicit permissions need to be given)
1666 1667 * Forums: topics can be locked so that no reply can be added
1667 1668 * Forums: topics can be marked as sticky so that they always appear at the top of the list
1668 1669 * Forums: attachments can now be added to replies
1669 1670 * Added time zone support
1670 1671 * Added a setting to choose the account activation strategy (available in application settings)
1671 1672 * Added 'Classic' theme (inspired from the v0.51 design)
1672 1673 * Added an alternate theme which provides issue list colorization based on issues priority
1673 1674 * Added Bazaar SCM adapter
1674 1675 * Added Annotate/Blame view in the repository browser (except for Darcs SCM)
1675 1676 * Diff style (inline or side by side) automatically saved as a user preference
1676 1677 * Added issues status changes on the activity view (by Cyril Mougel)
1677 1678 * Added forums topics on the activity view (disabled by default)
1678 1679 * Added an option on 'My account' for users who don't want to be notified of changes that they make
1679 1680 * Trac importer now supports mysql and postgresql databases
1680 1681 * Trac importer improvements (by Mat Trudel)
1681 1682 * 'fixed version' field can now be displayed on the issue list
1682 1683 * Added a couple of new formats for the 'date format' setting
1683 1684 * Added Traditional Chinese translation (by Shortie Lo)
1684 1685 * Added Russian translation (iGor kMeta)
1685 1686 * Project name format limitation removed (name can now contain any character)
1686 1687 * Project identifier maximum length changed from 12 to 20
1687 1688 * Changed the maximum length of LDAP account to 255 characters
1688 1689 * Removed the 12 characters limit on passwords
1689 1690 * Added wiki macros support
1690 1691 * Performance improvement on workflow setup screen
1691 1692 * More detailed html title on several views
1692 1693 * Custom fields can now be reordered
1693 1694 * Search engine: search can be restricted to an exact phrase by using quotation marks
1694 1695 * Added custom fields marked as 'For all projects' to the csv export of the cross project issue list
1695 1696 * Email notifications are now sent as Blind carbon copy by default
1696 1697 * Fixed: all members (including non active) should be deleted when deleting a project
1697 1698 * Fixed: Error on wiki syntax link (accessible from wiki/edit)
1698 1699 * Fixed: 'quick jump to a revision' form on the revisions list
1699 1700 * Fixed: error on admin/info if there's more than 1 plugin installed
1700 1701 * Fixed: svn or ldap password can be found in clear text in the html source in editing mode
1701 1702 * Fixed: 'Assigned to' drop down list is not sorted
1702 1703 * Fixed: 'View all issues' link doesn't work on issues/show
1703 1704 * Fixed: error on account/register when validation fails
1704 1705 * Fixed: Error when displaying the issue list if a float custom field is marked as 'used as filter'
1705 1706 * Fixed: Mercurial adapter breaks on missing :files entry in changeset hash (James Britt)
1706 1707 * Fixed: Wrong feed URLs on the home page
1707 1708 * Fixed: Update of time entry fails when the issue has been moved to an other project
1708 1709 * Fixed: Error when moving an issue without changing its tracker (Postgresql)
1709 1710 * Fixed: Changes not recorded when using :pserver string (CVS adapter)
1710 1711 * Fixed: admin should be able to move issues to any project
1711 1712 * Fixed: adding an attachment is not possible when changing the status of an issue
1712 1713 * Fixed: No mime-types in documents/files downloading
1713 1714 * Fixed: error when sorting the messages if there's only one board for the project
1714 1715 * Fixed: 'me' doesn't appear in the drop down filters on a project issue list.
1715 1716
1716 1717 == 2007-11-04 v0.6.0
1717 1718
1718 1719 * Permission model refactoring.
1719 1720 * Permissions: there are now 2 builtin roles that can be used to specify permissions given to other users than members of projects
1720 1721 * Permissions: some permissions (eg. browse the repository) can be removed for certain roles
1721 1722 * Permissions: modules (eg. issue tracking, news, documents...) can be enabled/disabled at project level
1722 1723 * Added Mantis and Trac importers
1723 1724 * New application layout
1724 1725 * Added "Bulk edit" functionality on the issue list
1725 1726 * More flexible mail notifications settings at user level
1726 1727 * Added AJAX based context menu on the project issue list that provide shortcuts for editing, re-assigning, changing the status or the priority, moving or deleting an issue
1727 1728 * Added the hability to copy an issue. It can be done from the "issue/show" view or from the context menu on the issue list
1728 1729 * Added the ability to customize issue list columns (at application level or for each saved query)
1729 1730 * Overdue versions (date reached and open issues > 0) are now always displayed on the roadmap
1730 1731 * Added the ability to rename wiki pages (specific permission required)
1731 1732 * Search engines now supports pagination. Results are sorted in reverse chronological order
1732 1733 * Added "Estimated hours" attribute on issues
1733 1734 * A category with assigned issue can now be deleted. 2 options are proposed: remove assignments or reassign issues to another category
1734 1735 * Forum notifications are now also sent to the authors of the thread, even if they donΓ―ΒΏΒ½t watch the board
1735 1736 * Added an application setting to specify the application protocol (http or https) used to generate urls in emails
1736 1737 * Gantt chart: now starts at the current month by default
1737 1738 * Gantt chart: month count and zoom factor are automatically saved as user preferences
1738 1739 * Wiki links can now refer to other project wikis
1739 1740 * Added wiki index by date
1740 1741 * Added preview on add/edit issue form
1741 1742 * Emails footer can now be customized from the admin interface (Admin -> Email notifications)
1742 1743 * Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that theyΓ―ΒΏΒ½re properly displayed)
1743 1744 * Calendar: first day of week can now be set in lang files
1744 1745 * Automatic closing of duplicate issues
1745 1746 * Added a cross-project issue list
1746 1747 * AJAXified the SCM browser (tree view)
1747 1748 * Pretty URL for the repository browser (Cyril Mougel)
1748 1749 * Search engine: added a checkbox to search titles only
1749 1750 * Added "% done" in the filter list
1750 1751 * Enumerations: values can now be reordered and a default value can be specified (eg. default issue priority)
1751 1752 * Added some accesskeys
1752 1753 * Added "Float" as a custom field format
1753 1754 * Added basic Theme support
1754 1755 * Added the ability to set the Γ―ΒΏΒ½done ratioΓ―ΒΏΒ½ of issues fixed by commit (Nikolay Solakov)
1755 1756 * Added custom fields in issue related mail notifications
1756 1757 * Email notifications are now sent in plain text and html
1757 1758 * Gantt chart can now be exported to a graphic file (png). This functionality is only available if RMagick is installed.
1758 1759 * Added syntax highlightment for repository files and wiki
1759 1760 * Improved automatic Redmine links
1760 1761 * Added automatic table of content support on wiki pages
1761 1762 * Added radio buttons on the documents list to sort documents by category, date, title or author
1762 1763 * Added basic plugin support, with a sample plugin
1763 1764 * Added a link to add a new category when creating or editing an issue
1764 1765 * Added a "Assignable" boolean on the Role model. If unchecked, issues can not be assigned to users having this role.
1765 1766 * Added an option to be able to relate issues in different projects
1766 1767 * Added the ability to move issues (to another project) without changing their trackers.
1767 1768 * Atom feeds added on project activity, news and changesets
1768 1769 * Added the ability to reset its own RSS access key
1769 1770 * Main project list now displays root projects with their subprojects
1770 1771 * Added anchor links to issue notes
1771 1772 * Added reposman Ruby version. This script can now register created repositories in Redmine (Nicolas Chuche)
1772 1773 * Issue notes are now included in search
1773 1774 * Added email sending test functionality
1774 1775 * Added LDAPS support for LDAP authentication
1775 1776 * Removed hard-coded URLs in mail templates
1776 1777 * Subprojects are now grouped by projects in the navigation drop-down menu
1777 1778 * Added a new value for date filters: this week
1778 1779 * Added cache for application settings
1779 1780 * Added Polish translation (Tomasz Gawryl)
1780 1781 * Added Czech translation (Jan Kadlecek)
1781 1782 * Added Romanian translation (Csongor Bartus)
1782 1783 * Added Hebrew translation (Bob Builder)
1783 1784 * Added Serbian translation (Dragan Matic)
1784 1785 * Added Korean translation (Choi Jong Yoon)
1785 1786 * Fixed: the link to delete issue relations is displayed even if the user is not authorized to delete relations
1786 1787 * Performance improvement on calendar and gantt
1787 1788 * Fixed: wiki preview doesnΓ―ΒΏΒ½t work on long entries
1788 1789 * Fixed: queries with multiple custom fields return no result
1789 1790 * Fixed: Can not authenticate user against LDAP if its DN contains non-ascii characters
1790 1791 * Fixed: URL with ~ broken in wiki formatting
1791 1792 * Fixed: some quotation marks are rendered as strange characters in pdf
1792 1793
1793 1794
1794 1795 == 2007-07-15 v0.5.1
1795 1796
1796 1797 * per project forums added
1797 1798 * added the ability to archive projects
1798 1799 * added Γ―ΒΏΒ½WatchΓ―ΒΏΒ½ functionality on issues. It allows users to receive notifications about issue changes
1799 1800 * custom fields for issues can now be used as filters on issue list
1800 1801 * added per user custom queries
1801 1802 * commit messages are now scanned for referenced or fixed issue IDs (keywords defined in Admin -> Settings)
1802 1803 * projects list now shows the list of public projects and private projects for which the user is a member
1803 1804 * versions can now be created with no date
1804 1805 * added issue count details for versions on Reports view
1805 1806 * added time report, by member/activity/tracker/version and year/month/week for the selected period
1806 1807 * each category can now be associated to a user, so that new issues in that category are automatically assigned to that user
1807 1808 * added autologin feature (disabled by default)
1808 1809 * optimistic locking added for wiki edits
1809 1810 * added wiki diff
1810 1811 * added the ability to destroy wiki pages (requires permission)
1811 1812 * a wiki page can now be attached to each version, and displayed on the roadmap
1812 1813 * attachments can now be added to wiki pages (original patch by Pavol Murin) and displayed online
1813 1814 * added an option to see all versions in the roadmap view (including completed ones)
1814 1815 * added basic issue relations
1815 1816 * added the ability to log time when changing an issue status
1816 1817 * account information can now be sent to the user when creating an account
1817 1818 * author and assignee of an issue always receive notifications (even if they turned of mail notifications)
1818 1819 * added a quick search form in page header
1819 1820 * added 'me' value for 'assigned to' and 'author' query filters
1820 1821 * added a link on revision screen to see the entire diff for the revision
1821 1822 * added last commit message for each entry in repository browser
1822 1823 * added the ability to view a file diff with free to/from revision selection.
1823 1824 * text files can now be viewed online when browsing the repository
1824 1825 * added basic support for other SCM: CVS (Ralph Vater), Mercurial and Darcs
1825 1826 * added fragment caching for svn diffs
1826 1827 * added fragment caching for calendar and gantt views
1827 1828 * login field automatically focused on login form
1828 1829 * subproject name displayed on issue list, calendar and gantt
1829 1830 * added an option to choose the date format: language based or ISO 8601
1830 1831 * added a simple mail handler. It lets users add notes to an existing issue by replying to the initial notification email.
1831 1832 * a 403 error page is now displayed (instead of a blank page) when trying to access a protected page
1832 1833 * added portuguese translation (Joao Carlos Clementoni)
1833 1834 * added partial online help japanese translation (Ken Date)
1834 1835 * added bulgarian translation (Nikolay Solakov)
1835 1836 * added dutch translation (Linda van den Brink)
1836 1837 * added swedish translation (Thomas Habets)
1837 1838 * italian translation update (Alessio Spadaro)
1838 1839 * japanese translation update (Satoru Kurashiki)
1839 1840 * fixed: error on history atom feed when thereΓ―ΒΏΒ½s no notes on an issue change
1840 1841 * fixed: error in journalizing an issue with longtext custom fields (Postgresql)
1841 1842 * fixed: creation of Oracle schema
1842 1843 * fixed: last day of the month not included in project activity
1843 1844 * fixed: files with an apostrophe in their names can't be accessed in SVN repository
1844 1845 * fixed: performance issue on RepositoriesController#revisions when a changeset has a great number of changes (eg. 100,000)
1845 1846 * fixed: open/closed issue counts are always 0 on reports view (postgresql)
1846 1847 * fixed: date query filters (wrong results and sql error with postgresql)
1847 1848 * fixed: confidentiality issue on account/show (private project names displayed to anyone)
1848 1849 * fixed: Long text custom fields displayed without line breaks
1849 1850 * fixed: Error when editing the wokflow after deleting a status
1850 1851 * fixed: SVN commit dates are now stored as local time
1851 1852
1852 1853
1853 1854 == 2007-04-11 v0.5.0
1854 1855
1855 1856 * added per project Wiki
1856 1857 * added rss/atom feeds at project level (custom queries can be used as feeds)
1857 1858 * added search engine (search in issues, news, commits, wiki pages, documents)
1858 1859 * simple time tracking functionality added
1859 1860 * added version due dates on calendar and gantt
1860 1861 * added subprojects issue count on project Reports page
1861 1862 * added the ability to copy an existing workflow when creating a new tracker
1862 1863 * added the ability to include subprojects on calendar and gantt
1863 1864 * added the ability to select trackers to display on calendar and gantt (Jeffrey Jones)
1864 1865 * added side by side svn diff view (Cyril Mougel)
1865 1866 * added back subproject filter on issue list
1866 1867 * added permissions report in admin area
1867 1868 * added a status filter on users list
1868 1869 * support for password-protected SVN repositories
1869 1870 * SVN commits are now stored in the database
1870 1871 * added simple svn statistics SVG graphs
1871 1872 * progress bars for roadmap versions (Nick Read)
1872 1873 * issue history now shows file uploads and deletions
1873 1874 * #id patterns are turned into links to issues in descriptions and commit messages
1874 1875 * japanese translation added (Satoru Kurashiki)
1875 1876 * chinese simplified translation added (Andy Wu)
1876 1877 * italian translation added (Alessio Spadaro)
1877 1878 * added scripts to manage SVN repositories creation and user access control using ssh+svn (Nicolas Chuche)
1878 1879 * better calendar rendering time
1879 1880 * fixed migration scripts to work with mysql 5 running in strict mode
1880 1881 * fixed: error when clicking "add" with no block selected on my/page_layout
1881 1882 * fixed: hard coded links in navigation bar
1882 1883 * fixed: table_name pre/suffix support
1883 1884
1884 1885
1885 1886 == 2007-02-18 v0.4.2
1886 1887
1887 1888 * Rails 1.2 is now required
1888 1889 * settings are now stored in the database and editable through the application in: Admin -> Settings (config_custom.rb is no longer used)
1889 1890 * added project roadmap view
1890 1891 * mail notifications added when a document, a file or an attachment is added
1891 1892 * tooltips added on Gantt chart and calender to view the details of the issues
1892 1893 * ability to set the sort order for roles, trackers, issue statuses
1893 1894 * added missing fields to csv export: priority, start date, due date, done ratio
1894 1895 * added total number of issues per tracker on project overview
1895 1896 * all icons replaced (new icons are based on GPL icon set: "KDE Crystal Diamond 2.5" -by paolino- and "kNeu! Alpha v0.1" -by Pablo Fabregat-)
1896 1897 * added back "fixed version" field on issue screen and in filters
1897 1898 * project settings screen split in 4 tabs
1898 1899 * custom fields screen split in 3 tabs (one for each kind of custom field)
1899 1900 * multiple issues pdf export now rendered as a table
1900 1901 * added a button on users/list to manually activate an account
1901 1902 * added a setting option to disable "password lost" functionality
1902 1903 * added a setting option to set max number of issues in csv/pdf exports
1903 1904 * fixed: subprojects count is always 0 on projects list
1904 1905 * fixed: locked users are proposed when adding a member to a project
1905 1906 * fixed: setting an issue status as default status leads to an sql error with SQLite
1906 1907 * fixed: unable to delete an issue status even if it's not used yet
1907 1908 * fixed: filters ignored when exporting a predefined query to csv/pdf
1908 1909 * fixed: crash when french "issue_edit" email notification is sent
1909 1910 * fixed: hide mail preference not saved (my/account)
1910 1911 * fixed: crash when a new user try to edit its "my page" layout
1911 1912
1912 1913
1913 1914 == 2007-01-03 v0.4.1
1914 1915
1915 1916 * fixed: emails have no recipient when one of the project members has notifications disabled
1916 1917
1917 1918
1918 1919 == 2007-01-02 v0.4.0
1919 1920
1920 1921 * simple SVN browser added (just needs svn binaries in PATH)
1921 1922 * comments can now be added on news
1922 1923 * "my page" is now customizable
1923 1924 * more powerfull and savable filters for issues lists
1924 1925 * improved issues change history
1925 1926 * new functionality: move an issue to another project or tracker
1926 1927 * new functionality: add a note to an issue
1927 1928 * new report: project activity
1928 1929 * "start date" and "% done" fields added on issues
1929 1930 * project calendar added
1930 1931 * gantt chart added (exportable to pdf)
1931 1932 * single/multiple issues pdf export added
1932 1933 * issues reports improvements
1933 1934 * multiple file upload for issues, documents and files
1934 1935 * option to set maximum size of uploaded files
1935 1936 * textile formating of issue and news descritions (RedCloth required)
1936 1937 * integration of DotClear jstoolbar for textile formatting
1937 1938 * calendar date picker for date fields (LGPL DHTML Calendar http://sourceforge.net/projects/jscalendar)
1938 1939 * new filter in issues list: Author
1939 1940 * ajaxified paginators
1940 1941 * news rss feed added
1941 1942 * option to set number of results per page on issues list
1942 1943 * localized csv separator (comma/semicolon)
1943 1944 * csv output encoded to ISO-8859-1
1944 1945 * user custom field displayed on account/show
1945 1946 * default configuration improved (default roles, trackers, status, permissions and workflows)
1946 1947 * language for default configuration data can now be chosen when running 'load_default_data' task
1947 1948 * javascript added on custom field form to show/hide fields according to the format of custom field
1948 1949 * fixed: custom fields not in csv exports
1949 1950 * fixed: project settings now displayed according to user's permissions
1950 1951 * fixed: application error when no version is selected on projects/add_file
1951 1952 * fixed: public actions not authorized for members of non public projects
1952 1953 * fixed: non public projects were shown on welcome screen even if current user is not a member
1953 1954
1954 1955
1955 1956 == 2006-10-08 v0.3.0
1956 1957
1957 1958 * user authentication against multiple LDAP (optional)
1958 1959 * token based "lost password" functionality
1959 1960 * user self-registration functionality (optional)
1960 1961 * custom fields now available for issues, users and projects
1961 1962 * new custom field format "text" (displayed as a textarea field)
1962 1963 * project & administration drop down menus in navigation bar for quicker access
1963 1964 * text formatting is preserved for long text fields (issues, projects and news descriptions)
1964 1965 * urls and emails are turned into clickable links in long text fields
1965 1966 * "due date" field added on issues
1966 1967 * tracker selection filter added on change log
1967 1968 * Localization plugin replaced with GLoc 1.1.0 (iconv required)
1968 1969 * error messages internationalization
1969 1970 * german translation added (thanks to Karim Trott)
1970 1971 * data locking for issues to prevent update conflicts (using ActiveRecord builtin optimistic locking)
1971 1972 * new filter in issues list: "Fixed version"
1972 1973 * active filters are displayed with colored background on issues list
1973 1974 * custom configuration is now defined in config/config_custom.rb
1974 1975 * user object no more stored in session (only user_id)
1975 1976 * news summary field is no longer required
1976 1977 * tables and forms redesign
1977 1978 * Fixed: boolean custom field not working
1978 1979 * Fixed: error messages for custom fields are not displayed
1979 1980 * Fixed: invalid custom fields should have a red border
1980 1981 * Fixed: custom fields values are not validated on issue update
1981 1982 * Fixed: unable to choose an empty value for 'List' custom fields
1982 1983 * Fixed: no issue categories sorting
1983 1984 * Fixed: incorrect versions sorting
1984 1985
1985 1986
1986 1987 == 2006-07-12 - v0.2.2
1987 1988
1988 1989 * Fixed: bug in "issues list"
1989 1990
1990 1991
1991 1992 == 2006-07-09 - v0.2.1
1992 1993
1993 1994 * new databases supported: Oracle, PostgreSQL, SQL Server
1994 1995 * projects/subprojects hierarchy (1 level of subprojects only)
1995 1996 * environment information display in admin/info
1996 1997 * more filter options in issues list (rev6)
1997 1998 * default language based on browser settings (Accept-Language HTTP header)
1998 1999 * issues list exportable to CSV (rev6)
1999 2000 * simple_format and auto_link on long text fields
2000 2001 * more data validations
2001 2002 * Fixed: error when all mail notifications are unchecked in admin/mail_options
2002 2003 * Fixed: all project news are displayed on project summary
2003 2004 * Fixed: Can't change user password in users/edit
2004 2005 * Fixed: Error on tables creation with PostgreSQL (rev5)
2005 2006 * Fixed: SQL error in "issue reports" view with PostgreSQL (rev5)
2006 2007
2007 2008
2008 2009 == 2006-06-25 - v0.1.0
2009 2010
2010 2011 * multiple users/multiple projects
2011 2012 * role based access control
2012 2013 * issue tracking system
2013 2014 * fully customizable workflow
2014 2015 * documents/files repository
2015 2016 * email notifications on issue creation and update
2016 2017 * multilanguage support (except for error messages):english, french, spanish
2017 2018 * online manual in french (unfinished)
@@ -1,4332 +1,4339
1 1 #============================================================+
2 2 # File name : tcpdf.rb
3 3 # Begin : 2002-08-03
4 4 # Last Update : 2007-03-20
5 5 # Author : Nicola Asuni
6 6 # Version : 1.53.0.TC031
7 7 # License : GNU LGPL (http://www.gnu.org/copyleft/lesser.html)
8 8 #
9 9 # Description : This is a Ruby class for generating PDF files
10 10 # on-the-fly without requiring external
11 11 # extensions.
12 12 #
13 13 # IMPORTANT:
14 14 # This class is an extension and improvement of the Public Domain
15 15 # FPDF class by Olivier Plathey (http://www.fpdf.org).
16 16 #
17 17 # Main changes by Nicola Asuni:
18 18 # Ruby porting;
19 19 # UTF-8 Unicode support;
20 20 # code refactoring;
21 21 # source code clean up;
22 22 # code style and formatting;
23 23 # source code documentation using phpDocumentor (www.phpdoc.org);
24 24 # All ISO page formats were included;
25 25 # image scale factor;
26 26 # includes methods to parse and printsome XHTML code, supporting the following elements: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small;
27 27 # includes a method to print various barcode formats using an improved version of "Generic Barcode Render Class" by Karim Mribti (http://www.mribti.com/barcode/) (require GD library: http://www.boutell.com/gd/);
28 28 # defines standard Header() and Footer() methods.
29 29 #
30 30 # Ported to Ruby by Ed Moss 2007-08-06
31 31 #
32 32 #============================================================+
33 33
34 34 #
35 35 # TCPDF Class.
36 36 # @package com.tecnick.tcpdf
37 37 #
38 38
39 39 @@version = "1.53.0.TC031"
40 40 @@fpdf_charwidths = {}
41 41
42 42 PDF_PRODUCER = 'TCPDF via RFPDF 1.53.0.TC031 (http://tcpdf.sourceforge.net)'
43 43
44 44 module TCPDFFontDescriptor
45 45 @@descriptors = { 'freesans' => {} }
46 46 @@font_name = 'freesans'
47 47
48 48 def self.font(font_name)
49 49 @@descriptors[font_name.gsub(".rb", "")]
50 50 end
51 51
52 52 def self.define(font_name = 'freesans')
53 53 @@descriptors[font_name] ||= {}
54 54 yield @@descriptors[font_name]
55 55 end
56 56 end
57 57
58 58 # This is a Ruby class for generating PDF files on-the-fly without requiring external extensions.<br>
59 59 # This class is an extension and improvement of the FPDF class by Olivier Plathey (http://www.fpdf.org).<br>
60 60 # This version contains some changes: [porting to Ruby, support for UTF-8 Unicode, code style and formatting, php documentation (www.phpdoc.org), ISO page formats, minor improvements, image scale factor]<br>
61 61 # TCPDF project (http://tcpdf.sourceforge.net) is based on the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org).<br>
62 62 # To add your own TTF fonts please read /fonts/README.TXT
63 63 # @name TCPDF
64 64 # @package com.tecnick.tcpdf
65 65 # @@version 1.53.0.TC031
66 66 # @author Nicola Asuni
67 67 # @link http://tcpdf.sourceforge.net
68 68 # @license http://www.gnu.org/copyleft/lesser.html LGPL
69 69 #
70 70 class TCPDF
71 71 include RFPDF
72 72 include Core::RFPDF
73 73 include RFPDF::Math
74 74
75 75 def logger
76 76 Rails.logger
77 77 end
78 78
79 79 cattr_accessor :k_cell_height_ratio
80 80 @@k_cell_height_ratio = 1.25
81 81
82 82 cattr_accessor :k_blank_image
83 83 @@k_blank_image = ""
84 84
85 85 cattr_accessor :k_small_ratio
86 86 @@k_small_ratio = 2/3.0
87 87
88 88 cattr_accessor :k_path_cache
89 89 @@k_path_cache = Rails.root.join('tmp')
90 90
91 91 cattr_accessor :k_path_url_cache
92 92 @@k_path_url_cache = Rails.root.join('tmp')
93 93
94 94 cattr_accessor :decoder
95 95
96 96 attr_accessor :barcode
97 97
98 98 attr_accessor :buffer
99 99
100 100 attr_accessor :diffs
101 101
102 102 attr_accessor :color_flag
103 103
104 104 attr_accessor :default_table_columns
105 105
106 106 attr_accessor :max_table_columns
107 107
108 108 attr_accessor :default_font
109 109
110 110 attr_accessor :draw_color
111 111
112 112 attr_accessor :encoding
113 113
114 114 attr_accessor :fill_color
115 115
116 116 attr_accessor :fonts
117 117
118 118 attr_accessor :font_family
119 119
120 120 attr_accessor :font_files
121 121
122 122 cattr_accessor :font_path
123 123
124 124 attr_accessor :font_style
125 125
126 126 attr_accessor :font_size_pt
127 127
128 128 attr_accessor :header_width
129 129
130 130 attr_accessor :header_logo
131 131
132 132 attr_accessor :header_logo_width
133 133
134 134 attr_accessor :header_title
135 135
136 136 attr_accessor :header_string
137 137
138 138 attr_accessor :images
139 139
140 140 attr_accessor :img_scale
141 141
142 142 attr_accessor :in_footer
143 143
144 144 attr_accessor :is_unicode
145 145
146 146 attr_accessor :lasth
147 147
148 148 attr_accessor :links
149 149
150 150 attr_accessor :list_ordered
151 151
152 152 attr_accessor :list_count
153 153
154 154 attr_accessor :li_spacer
155 155
156 156 attr_accessor :n
157 157
158 158 attr_accessor :offsets
159 159
160 160 attr_accessor :orientation_changes
161 161
162 162 attr_accessor :page
163 163
164 164 attr_accessor :page_links
165 165
166 166 attr_accessor :pages
167 167
168 168 attr_accessor :pdf_version
169 169
170 170 attr_accessor :prevfill_color
171 171
172 172 attr_accessor :prevtext_color
173 173
174 174 attr_accessor :print_header
175 175
176 176 attr_accessor :print_footer
177 177
178 178 attr_accessor :state
179 179
180 180 attr_accessor :tableborder
181 181
182 182 attr_accessor :tdbegin
183 183
184 184 attr_accessor :tdwidth
185 185
186 186 attr_accessor :tdheight
187 187
188 188 attr_accessor :tdalign
189 189
190 190 attr_accessor :tdfill
191 191
192 192 attr_accessor :tempfontsize
193 193
194 194 attr_accessor :text_color
195 195
196 196 attr_accessor :underline
197 197
198 198 attr_accessor :ws
199 199
200 200 #
201 201 # This is the class constructor.
202 202 # It allows to set up the page format, the orientation and
203 203 # the measure unit used in all the methods (except for the font sizes).
204 204 # @since 1.0
205 205 # @param string :orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li></ul>
206 206 # @param string :unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
207 207 # @param mixed :format The format used for pages. It can be either one of the following values (case insensitive) or a custom format in the form of a two-element array containing the width and the height (expressed in the unit given by unit).<ul><li>4A0</li><li>2A0</li><li>A0</li><li>A1</li><li>A2</li><li>A3</li><li>A4 (default)</li><li>A5</li><li>A6</li><li>A7</li><li>A8</li><li>A9</li><li>A10</li><li>B0</li><li>B1</li><li>B2</li><li>B3</li><li>B4</li><li>B5</li><li>B6</li><li>B7</li><li>B8</li><li>B9</li><li>B10</li><li>C0</li><li>C1</li><li>C2</li><li>C3</li><li>C4</li><li>C5</li><li>C6</li><li>C7</li><li>C8</li><li>C9</li><li>C10</li><li>RA0</li><li>RA1</li><li>RA2</li><li>RA3</li><li>RA4</li><li>SRA0</li><li>SRA1</li><li>SRA2</li><li>SRA3</li><li>SRA4</li><li>LETTER</li><li>LEGAL</li><li>EXECUTIVE</li><li>FOLIO</li></ul>
208 208 # @param boolean :unicode TRUE means that the input text is unicode (default = true)
209 209 # @param String :encoding charset encoding; default is UTF-8
210 210 #
211 211 def initialize(orientation = 'P', unit = 'mm', format = 'A4', unicode = true, encoding = "UTF-8")
212 212
213 213 # Set internal character encoding to ASCII#
214 214 #FIXME 2007-05-25 (EJM) Level=0 -
215 215 # if (respond_to?("mb_internal_encoding") and mb_internal_encoding())
216 216 # @internal_encoding = mb_internal_encoding();
217 217 # mb_internal_encoding("ASCII");
218 218 # }
219 219
220 220 #Some checks
221 221 dochecks();
222 222
223 223 begin
224 224 @@decoder = HTMLEntities.new
225 225 rescue
226 226 @@decoder = nil
227 227 end
228 228
229 229 #Initialization of properties
230 230 @barcode ||= false
231 231 @buffer ||= ''
232 232 @diffs ||= []
233 233 @color_flag ||= false
234 234 @default_table_columns ||= 4
235 235 @table_columns ||= 0
236 236 @max_table_columns ||= []
237 237 @tr_id ||= 0
238 238 @max_td_page ||= []
239 239 @max_td_y ||= []
240 240 @t_columns ||= 0
241 241 @default_font ||= "FreeSans" if unicode
242 242 @default_font ||= "Helvetica"
243 243 @draw_color ||= '0 G'
244 244 @encoding ||= "UTF-8"
245 245 @fill_color ||= '0 g'
246 246 @fonts ||= {}
247 247 @font_family ||= ''
248 248 @font_files ||= {}
249 249 @font_style ||= ''
250 250 @font_size ||= 12
251 251 @font_size_pt ||= 12
252 252 @header_width ||= 0
253 253 @header_logo ||= ""
254 254 @header_logo_width ||= 30
255 255 @header_title ||= ""
256 256 @header_string ||= ""
257 257 @images ||= {}
258 258 @img_scale ||= 1
259 259 @in_footer ||= false
260 260 @is_unicode = unicode
261 261 @lasth ||= 0
262 262 @links ||= []
263 263 @list_ordered ||= []
264 264 @list_count ||= []
265 265 @li_spacer ||= ""
266 266 @li_count ||= 0
267 267 @spacer ||= ""
268 268 @quote_count ||= 0
269 269 @prevquote_count ||= 0
270 270 @quote_top ||= []
271 271 @quote_page ||= []
272 272 @n ||= 2
273 273 @offsets ||= []
274 274 @orientation_changes ||= []
275 275 @page ||= 0
276 276 @page_links ||= {}
277 277 @pages ||= []
278 278 @pdf_version ||= "1.3"
279 279 @prevfill_color ||= [255,255,255]
280 280 @prevtext_color ||= [0,0,0]
281 281 @print_header ||= false
282 282 @print_footer ||= false
283 283 @state ||= 0
284 284 @tableborder ||= 0
285 285 @tdbegin ||= false
286 @tdtext ||= ''
286 287 @tdwidth ||= 0
287 288 @tdheight ||= 0
288 289 @tdalign ||= "L"
289 290 @tdfill ||= 0
290 291 @tempfontsize ||= 10
291 292 @text_color ||= '0 g'
292 293 @underline ||= false
293 294 @deleted ||= false
294 295 @ws ||= 0
295 296
296 297 #Standard Unicode fonts
297 298 @core_fonts = {
298 299 'courier'=>'Courier',
299 300 'courierB'=>'Courier-Bold',
300 301 'courierI'=>'Courier-Oblique',
301 302 'courierBI'=>'Courier-BoldOblique',
302 303 'helvetica'=>'Helvetica',
303 304 'helveticaB'=>'Helvetica-Bold',
304 305 'helveticaI'=>'Helvetica-Oblique',
305 306 'helveticaBI'=>'Helvetica-BoldOblique',
306 307 'times'=>'Times-Roman',
307 308 'timesB'=>'Times-Bold',
308 309 'timesI'=>'Times-Italic',
309 310 'timesBI'=>'Times-BoldItalic',
310 311 'symbol'=>'Symbol',
311 312 'zapfdingbats'=>'ZapfDingbats'}
312 313
313 314 #Scale factor
314 315 case unit.downcase
315 316 when 'pt' ; @k=1
316 317 when 'mm' ; @k=72/25.4
317 318 when 'cm' ; @k=72/2.54
318 319 when 'in' ; @k=72
319 320 else Error("Incorrect unit: #{unit}")
320 321 end
321 322
322 323 #Page format
323 324 if format.is_a?(String)
324 325 # Page formats (45 standard ISO paper formats and 4 american common formats).
325 326 # Paper cordinates are calculated in this way: (inches# 72) where (1 inch = 2.54 cm)
326 327 case (format.upcase)
327 328 when '4A0' ; format = [4767.87,6740.79]
328 329 when '2A0' ; format = [3370.39,4767.87]
329 330 when 'A0' ; format = [2383.94,3370.39]
330 331 when 'A1' ; format = [1683.78,2383.94]
331 332 when 'A2' ; format = [1190.55,1683.78]
332 333 when 'A3' ; format = [841.89,1190.55]
333 334 when 'A4' ; format = [595.28,841.89] # ; default
334 335 when 'A5' ; format = [419.53,595.28]
335 336 when 'A6' ; format = [297.64,419.53]
336 337 when 'A7' ; format = [209.76,297.64]
337 338 when 'A8' ; format = [147.40,209.76]
338 339 when 'A9' ; format = [104.88,147.40]
339 340 when 'A10' ; format = [73.70,104.88]
340 341 when 'B0' ; format = [2834.65,4008.19]
341 342 when 'B1' ; format = [2004.09,2834.65]
342 343 when 'B2' ; format = [1417.32,2004.09]
343 344 when 'B3' ; format = [1000.63,1417.32]
344 345 when 'B4' ; format = [708.66,1000.63]
345 346 when 'B5' ; format = [498.90,708.66]
346 347 when 'B6' ; format = [354.33,498.90]
347 348 when 'B7' ; format = [249.45,354.33]
348 349 when 'B8' ; format = [175.75,249.45]
349 350 when 'B9' ; format = [124.72,175.75]
350 351 when 'B10' ; format = [87.87,124.72]
351 352 when 'C0' ; format = [2599.37,3676.54]
352 353 when 'C1' ; format = [1836.85,2599.37]
353 354 when 'C2' ; format = [1298.27,1836.85]
354 355 when 'C3' ; format = [918.43,1298.27]
355 356 when 'C4' ; format = [649.13,918.43]
356 357 when 'C5' ; format = [459.21,649.13]
357 358 when 'C6' ; format = [323.15,459.21]
358 359 when 'C7' ; format = [229.61,323.15]
359 360 when 'C8' ; format = [161.57,229.61]
360 361 when 'C9' ; format = [113.39,161.57]
361 362 when 'C10' ; format = [79.37,113.39]
362 363 when 'RA0' ; format = [2437.80,3458.27]
363 364 when 'RA1' ; format = [1729.13,2437.80]
364 365 when 'RA2' ; format = [1218.90,1729.13]
365 366 when 'RA3' ; format = [864.57,1218.90]
366 367 when 'RA4' ; format = [609.45,864.57]
367 368 when 'SRA0' ; format = [2551.18,3628.35]
368 369 when 'SRA1' ; format = [1814.17,2551.18]
369 370 when 'SRA2' ; format = [1275.59,1814.17]
370 371 when 'SRA3' ; format = [907.09,1275.59]
371 372 when 'SRA4' ; format = [637.80,907.09]
372 373 when 'LETTER' ; format = [612.00,792.00]
373 374 when 'LEGAL' ; format = [612.00,1008.00]
374 375 when 'EXECUTIVE' ; format = [521.86,756.00]
375 376 when 'FOLIO' ; format = [612.00,936.00]
376 377 #else then Error("Unknown page format: #{format}"
377 378 end
378 379 @fw_pt = format[0]
379 380 @fh_pt = format[1]
380 381 else
381 382 @fw_pt = format[0]*@k
382 383 @fh_pt = format[1]*@k
383 384 end
384 385
385 386 @fw = @fw_pt/@k
386 387 @fh = @fh_pt/@k
387 388
388 389 #Page orientation
389 390 orientation = orientation.downcase
390 391 if orientation == 'p' or orientation == 'portrait'
391 392 @def_orientation = 'P'
392 393 @w_pt = @fw_pt
393 394 @h_pt = @fh_pt
394 395 elsif orientation == 'l' or orientation == 'landscape'
395 396 @def_orientation = 'L'
396 397 @w_pt = @fh_pt
397 398 @h_pt = @fw_pt
398 399 else
399 400 Error("Incorrect orientation: #{orientation}")
400 401 end
401 402
402 403 @cur_orientation = @def_orientation
403 404 @w = @w_pt/@k
404 405 @h = @h_pt/@k
405 406 #Page margins (1 cm)
406 407 margin = 28.35/@k
407 408 SetMargins(margin, margin)
408 409 #Interior cell margin (1 mm)
409 410 @c_margin = margin / 10
410 411 #Line width (0.2 mm)
411 412 @line_width = 0.567 / @k
412 413 #Automatic page break
413 414 SetAutoPageBreak(true, 2 * margin)
414 415 #Full width display mode
415 416 SetDisplayMode('fullwidth')
416 417 #Compression
417 418 SetCompression(true)
418 419 #Set default PDF version number
419 420 @pdf_version = "1.3"
420 421
421 422 @encoding = encoding
422 423 @b = 0
423 424 @i = 0
424 425 @u = 0
425 426 @href = ''
426 427 @fontlist = ["arial", "times", "courier", "helvetica", "symbol"]
427 428 @issetfont = false
428 429 @issetcolor = false
429 430
430 431 SetFillColor(200, 200, 200, true)
431 432 SetTextColor(0, 0, 0, true)
432 433 end
433 434
434 435 #
435 436 # Set the image scale.
436 437 # @param float :scale image scale.
437 438 # @author Nicola Asuni
438 439 # @since 1.5.2
439 440 #
440 441 def SetImageScale(scale)
441 442 @img_scale = scale;
442 443 end
443 444 alias_method :set_image_scale, :SetImageScale
444 445
445 446 #
446 447 # Returns the image scale.
447 448 # @return float image scale.
448 449 # @author Nicola Asuni
449 450 # @since 1.5.2
450 451 #
451 452 def GetImageScale()
452 453 return @img_scale;
453 454 end
454 455 alias_method :get_image_scale, :GetImageScale
455 456
456 457 #
457 458 # Returns the page width in units.
458 459 # @return int page width.
459 460 # @author Nicola Asuni
460 461 # @since 1.5.2
461 462 #
462 463 def GetPageWidth()
463 464 return @w;
464 465 end
465 466 alias_method :get_page_width, :GetPageWidth
466 467
467 468 #
468 469 # Returns the page height in units.
469 470 # @return int page height.
470 471 # @author Nicola Asuni
471 472 # @since 1.5.2
472 473 #
473 474 def GetPageHeight()
474 475 return @h;
475 476 end
476 477 alias_method :get_page_height, :GetPageHeight
477 478
478 479 #
479 480 # Returns the page break margin.
480 481 # @return int page break margin.
481 482 # @author Nicola Asuni
482 483 # @since 1.5.2
483 484 #
484 485 def GetBreakMargin()
485 486 return @b_margin;
486 487 end
487 488 alias_method :get_break_margin, :GetBreakMargin
488 489
489 490 #
490 491 # Returns the scale factor (number of points in user unit).
491 492 # @return int scale factor.
492 493 # @author Nicola Asuni
493 494 # @since 1.5.2
494 495 #
495 496 def GetScaleFactor()
496 497 return @k;
497 498 end
498 499 alias_method :get_scale_factor, :GetScaleFactor
499 500
500 501 #
501 502 # Defines the left, top and right margins. By default, they equal 1 cm. Call this method to change them.
502 503 # @param float :left Left margin.
503 504 # @param float :top Top margin.
504 505 # @param float :right Right margin. Default value is the left one.
505 506 # @since 1.0
506 507 # @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
507 508 #
508 509 def SetMargins(left, top, right=-1)
509 510 #Set left, top and right margins
510 511 @l_margin = left
511 512 @t_margin = top
512 513 if (right == -1)
513 514 right = left
514 515 end
515 516 @r_margin = right
516 517 end
517 518 alias_method :set_margins, :SetMargins
518 519
519 520 #
520 521 # Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin.
521 522 # @param float :margin The margin.
522 523 # @since 1.4
523 524 # @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
524 525 #
525 526 def SetLeftMargin(margin)
526 527 #Set left margin
527 528 @l_margin = margin
528 529 if ((@page>0) and (@x < margin))
529 530 @x = margin
530 531 end
531 532 end
532 533 alias_method :set_left_margin, :SetLeftMargin
533 534
534 535 #
535 536 # Defines the top margin. The method can be called before creating the first page.
536 537 # @param float :margin The margin.
537 538 # @since 1.5
538 539 # @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
539 540 #
540 541 def SetTopMargin(margin)
541 542 #Set top margin
542 543 @t_margin = margin
543 544 end
544 545 alias_method :set_top_margin, :SetTopMargin
545 546
546 547 #
547 548 # Defines the right margin. The method can be called before creating the first page.
548 549 # @param float :margin The margin.
549 550 # @since 1.5
550 551 # @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
551 552 #
552 553 def SetRightMargin(margin)
553 554 #Set right margin
554 555 @r_margin = margin
555 556 end
556 557 alias_method :set_right_margin, :SetRightMargin
557 558
558 559 #
559 560 # Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm.
560 561 # @param boolean :auto Boolean indicating if mode should be on or off.
561 562 # @param float :margin Distance from the bottom of the page.
562 563 # @since 1.0
563 564 # @see Cell(), MultiCell(), AcceptPageBreak()
564 565 #
565 566 def SetAutoPageBreak(auto, margin=0)
566 567 #Set auto page break mode and triggering margin
567 568 @auto_page_break = auto
568 569 @b_margin = margin
569 570 @page_break_trigger = @h - margin
570 571 end
571 572 alias_method :set_auto_page_break, :SetAutoPageBreak
572 573
573 574 #
574 575 # Defines the way the document is to be displayed by the viewer. The zoom level can be set: pages can be displayed entirely on screen, occupy the full width of the window, use real size, be scaled by a specific zooming factor or use viewer default (configured in the Preferences menu of Acrobat). The page layout can be specified too: single at once, continuous display, two columns or viewer default. By default, documents use the full width mode with continuous display.
575 576 # @param mixed :zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
576 577 # @param string :layout The page layout. Possible values are:<ul><li>single: displays one page at once</li><li>continuous: displays pages continuously (default)</li><li>two: displays two pages on two columns</li><li>default: uses viewer default mode</li></ul>
577 578 # @since 1.2
578 579 #
579 580 def SetDisplayMode(zoom, layout = 'continuous')
580 581 #Set display mode in viewer
581 582 if (zoom == 'fullpage' or zoom == 'fullwidth' or zoom == 'real' or zoom == 'default' or !zoom.is_a?(String))
582 583 @zoom_mode = zoom
583 584 else
584 585 Error("Incorrect zoom display mode: #{zoom}")
585 586 end
586 587 if (layout == 'single' or layout == 'continuous' or layout == 'two' or layout == 'default')
587 588 @layout_mode = layout
588 589 else
589 590 Error("Incorrect layout display mode: #{layout}")
590 591 end
591 592 end
592 593 alias_method :set_display_mode, :SetDisplayMode
593 594
594 595 #
595 596 # Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default.
596 597 # Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
597 598 # @param boolean :compress Boolean indicating if compression must be enabled.
598 599 # @since 1.4
599 600 #
600 601 def SetCompression(compress)
601 602 #Set page compression
602 603 if (respond_to?('gzcompress'))
603 604 @compress = compress
604 605 else
605 606 @compress = false
606 607 end
607 608 end
608 609 alias_method :set_compression, :SetCompression
609 610
610 611 #
611 612 # Defines the title of the document.
612 613 # @param string :title The title.
613 614 # @since 1.2
614 615 # @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
615 616 #
616 617 def SetTitle(title)
617 618 #Title of document
618 619 @title = title
619 620 end
620 621 alias_method :set_title, :SetTitle
621 622
622 623 #
623 624 # Defines the subject of the document.
624 625 # @param string :subject The subject.
625 626 # @since 1.2
626 627 # @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
627 628 #
628 629 def SetSubject(subject)
629 630 #Subject of document
630 631 @subject = subject
631 632 end
632 633 alias_method :set_subject, :SetSubject
633 634
634 635 #
635 636 # Defines the author of the document.
636 637 # @param string :author The name of the author.
637 638 # @since 1.2
638 639 # @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
639 640 #
640 641 def SetAuthor(author)
641 642 #Author of document
642 643 @author = author
643 644 end
644 645 alias_method :set_author, :SetAuthor
645 646
646 647 #
647 648 # Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
648 649 # @param string :keywords The list of keywords.
649 650 # @since 1.2
650 651 # @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
651 652 #
652 653 def SetKeywords(keywords)
653 654 #Keywords of document
654 655 @keywords = keywords
655 656 end
656 657 alias_method :set_keywords, :SetKeywords
657 658
658 659 #
659 660 # Defines the creator of the document. This is typically the name of the application that generates the PDF.
660 661 # @param string :creator The name of the creator.
661 662 # @since 1.2
662 663 # @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
663 664 #
664 665 def SetCreator(creator)
665 666 #Creator of document
666 667 @creator = creator
667 668 end
668 669 alias_method :set_creator, :SetCreator
669 670
670 671 #
671 672 # Defines an alias for the total number of pages. It will be substituted as the document is closed.<br />
672 673 # <b>Example:</b><br />
673 674 # <pre>
674 675 # class PDF extends TCPDF {
675 676 # def Footer()
676 677 # #Go to 1.5 cm from bottom
677 678 # SetY(-15);
678 679 # #Select Arial italic 8
679 680 # SetFont('Arial','I',8);
680 681 # #Print current and total page numbers
681 682 # Cell(0,10,'Page '.PageNo().'/{nb}',0,0,'C');
682 683 # end
683 684 # }
684 685 # :pdf=new PDF();
685 686 # :pdf->alias_nb_pages();
686 687 # </pre>
687 688 # @param string :alias The alias. Default valuenb}.
688 689 # @since 1.4
689 690 # @see PageNo(), Footer()
690 691 #
691 692 def AliasNbPages(alias_nb ='{nb}')
692 693 #Define an alias for total number of pages
693 694 @alias_nb_pages = escapetext(alias_nb)
694 695 end
695 696 alias_method :alias_nb_pages, :AliasNbPages
696 697
697 698 #
698 699 # This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid.
699 700 # 2004-06-11 :: Nicola Asuni : changed bold tag with strong
700 701 # @param string :msg The error message
701 702 # @since 1.0
702 703 #
703 704 def Error(msg)
704 705 #Fatal error
705 706 raise ("TCPDF error: #{msg}")
706 707 end
707 708 alias_method :error, :Error
708 709
709 710 #
710 711 # This method begins the generation of the PDF document. It is not necessary to call it explicitly because AddPage() does it automatically.
711 712 # Note: no page is created by this method
712 713 # @since 1.0
713 714 # @see AddPage(), Close()
714 715 #
715 716 def Open()
716 717 #Begin document
717 718 @state = 1
718 719 end
719 720 # alias_method :open, :Open
720 721
721 722 #
722 723 # Terminates the PDF document. It is not necessary to call this method explicitly because Output() does it automatically. If the document contains no page, AddPage() is called to prevent from getting an invalid document.
723 724 # @since 1.0
724 725 # @see Open(), Output()
725 726 #
726 727 def Close()
727 728 #Terminate document
728 729 if (@state==3)
729 730 return;
730 731 end
731 732 if (@page==0)
732 733 AddPage();
733 734 end
734 735 #Page footer
735 736 @in_footer=true;
736 737 Footer();
737 738 @in_footer=false;
738 739 #Close page
739 740 endpage();
740 741 #Close document
741 742 enddoc();
742 743 end
743 744 # alias_method :close, :Close
744 745
745 746 #
746 747 # Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer. Then the page is added, the current position set to the top-left corner according to the left and top margins, and Header() is called to display the header.
747 748 # The font which was set before calling is automatically restored. There is no need to call SetFont() again if you want to continue with the same font. The same is true for colors and line width.
748 749 # The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
749 750 # @param string :orientation Page orientation. Possible values are (case insensitive):<ul><li>P or Portrait</li><li>L or Landscape</li></ul> The default value is the one passed to the constructor.
750 751 # @since 1.0
751 752 # @see TCPDF(), Header(), Footer(), SetMargins()
752 753 #
753 754 def AddPage(orientation='')
754 755 #Start a new page
755 756 if (@state==0)
756 757 Open();
757 758 end
758 759 family=@font_family;
759 760 style=@font_style + (@underline ? 'U' : '') + (@deleted ? 'D' : '');
760 761 size=@font_size_pt;
761 762 lw=@line_width;
762 763 dc=@draw_color;
763 764 fc=@fill_color;
764 765 tc=@text_color;
765 766 cf=@color_flag;
766 767 if (@page>0)
767 768 #Page footer
768 769 @in_footer=true;
769 770 Footer();
770 771 @in_footer=false;
771 772 #Close page
772 773 endpage();
773 774 end
774 775 #Start new page
775 776 beginpage(orientation);
776 777 #Set line cap style to square
777 778 out('2 J');
778 779 #Set line width
779 780 @line_width = lw;
780 781 out(sprintf('%.2f w', lw*@k));
781 782 #Set font
782 783 if (family)
783 784 SetFont(family, style, size);
784 785 end
785 786 #Set colors
786 787 @draw_color = dc;
787 788 if (dc!='0 G')
788 789 out(dc);
789 790 end
790 791 @fill_color = fc;
791 792 if (fc!='0 g')
792 793 out(fc);
793 794 end
794 795 @text_color = tc;
795 796 @color_flag = cf;
796 797 #Page header
797 798 Header();
798 799 #Restore line width
799 800 if (@line_width != lw)
800 801 @line_width = lw;
801 802 out(sprintf('%.2f w', lw*@k));
802 803 end
803 804 #Restore font
804 805 if (family)
805 806 SetFont(family, style, size);
806 807 end
807 808 #Restore colors
808 809 if (@draw_color != dc)
809 810 @draw_color = dc;
810 811 out(dc);
811 812 end
812 813 if (@fill_color != fc)
813 814 @fill_color = fc;
814 815 out(fc);
815 816 end
816 817 @text_color = tc;
817 818 @color_flag = cf;
818 819 end
819 820 alias_method :add_page, :AddPage
820 821
821 822 #
822 823 # Rotate object.
823 824 # @param float :angle angle in degrees for counter-clockwise rotation
824 825 # @param int :x abscissa of the rotation center. Default is current x position
825 826 # @param int :y ordinate of the rotation center. Default is current y position
826 827 #
827 828 def Rotate(angle, x="", y="")
828 829
829 830 if (x == '')
830 831 x = @x;
831 832 end
832 833
833 834 if (y == '')
834 835 y = @y;
835 836 end
836 837
837 838 if (@rtl)
838 839 x = @w - x;
839 840 angle = -@angle;
840 841 end
841 842
842 843 y = (@h - y) * @k;
843 844 x *= @k;
844 845
845 846 # calculate elements of transformation matrix
846 847 tm = []
847 848 tm[0] = ::Math::cos(deg2rad(angle));
848 849 tm[1] = ::Math::sin(deg2rad(angle));
849 850 tm[2] = -tm[1];
850 851 tm[3] = tm[0];
851 852 tm[4] = x + tm[1] * y - tm[0] * x;
852 853 tm[5] = y - tm[0] * y - tm[1] * x;
853 854
854 855 # generate the transformation matrix
855 856 Transform(tm);
856 857 end
857 858 alias_method :rotate, :Rotate
858 859
859 860 #
860 861 # Starts a 2D tranformation saving current graphic state.
861 862 # This function must be called before scaling, mirroring, translation, rotation and skewing.
862 863 # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
863 864 #
864 865 def StartTransform
865 866 out('q');
866 867 end
867 868 alias_method :start_transform, :StartTransform
868 869
869 870 #
870 871 # Stops a 2D tranformation restoring previous graphic state.
871 872 # This function must be called after scaling, mirroring, translation, rotation and skewing.
872 873 # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
873 874 #
874 875 def StopTransform
875 876 out('Q');
876 877 end
877 878 alias_method :stop_transform, :StopTransform
878 879
879 880 #
880 881 # Apply graphic transformations.
881 882 # @since 2.1.000 (2008-01-07)
882 883 # @see StartTransform(), StopTransform()
883 884 #
884 885 def Transform(tm)
885 886 x = out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', tm[0], tm[1], tm[2], tm[3], tm[4], tm[5]));
886 887 end
887 888 alias_method :transform, :Transform
888 889
889 890 #
890 891 # Set header data.
891 892 # @param string :ln header image logo
892 893 # @param string :lw header image logo width in mm
893 894 # @param string :ht string to print as title on document header
894 895 # @param string :hs string to print on document header
895 896 #
896 897 def SetHeaderData(ln="", lw=0, ht="", hs="")
897 898 @header_logo = ln || ""
898 899 @header_logo_width = lw || 0
899 900 @header_title = ht || ""
900 901 @header_string = hs || ""
901 902 end
902 903 alias_method :set_header_data, :SetHeaderData
903 904
904 905 #
905 906 # Set header margin.
906 907 # (minimum distance between header and top page margin)
907 908 # @param int :hm distance in millimeters
908 909 #
909 910 def SetHeaderMargin(hm=10)
910 911 @header_margin = hm;
911 912 end
912 913 alias_method :set_header_margin, :SetHeaderMargin
913 914
914 915 #
915 916 # Set footer margin.
916 917 # (minimum distance between footer and bottom page margin)
917 918 # @param int :fm distance in millimeters
918 919 #
919 920 def SetFooterMargin(fm=10)
920 921 @footer_margin = fm;
921 922 end
922 923 alias_method :set_footer_margin, :SetFooterMargin
923 924
924 925 #
925 926 # Set a flag to print page header.
926 927 # @param boolean :val set to true to print the page header (default), false otherwise.
927 928 #
928 929 def SetPrintHeader(val=true)
929 930 @print_header = val;
930 931 end
931 932 alias_method :set_print_header, :SetPrintHeader
932 933
933 934 #
934 935 # Set a flag to print page footer.
935 936 # @param boolean :value set to true to print the page footer (default), false otherwise.
936 937 #
937 938 def SetPrintFooter(val=true)
938 939 @print_footer = val;
939 940 end
940 941 alias_method :set_print_footer, :SetPrintFooter
941 942
942 943 #
943 944 # This method is used to render the page header.
944 945 # It is automatically called by AddPage() and could be overwritten in your own inherited class.
945 946 #
946 947 def Header()
947 948 if (@print_header)
948 949 if (@original_l_margin.nil?)
949 950 @original_l_margin = @l_margin;
950 951 end
951 952 if (@original_r_margin.nil?)
952 953 @original_r_margin = @r_margin;
953 954 end
954 955
955 956 #set current position
956 957 SetXY(@original_l_margin, @header_margin);
957 958
958 959 if ((@header_logo) and (@header_logo != @@k_blank_image))
959 960 Image(@header_logo, @original_l_margin, @header_margin, @header_logo_width);
960 961 else
961 962 @img_rb_y = GetY();
962 963 end
963 964
964 965 cell_height = ((@@k_cell_height_ratio * @header_font[2]) / @k).round(2)
965 966
966 967 header_x = @original_l_margin + (@header_logo_width * 1.05); #set left margin for text data cell
967 968
968 969 # header title
969 970 SetFont(@header_font[0], 'B', @header_font[2] + 1);
970 971 SetX(header_x);
971 972 Cell(@header_width, cell_height, @header_title, 0, 1, 'L');
972 973
973 974 # header string
974 975 SetFont(@header_font[0], @header_font[1], @header_font[2]);
975 976 SetX(header_x);
976 977 MultiCell(@header_width, cell_height, @header_string, 0, 'L', 0);
977 978
978 979 # print an ending header line
979 980 if (@header_width)
980 981 #set style for cell border
981 982 SetLineWidth(0.3);
982 983 SetDrawColor(0, 0, 0);
983 984 SetY(1 + (@img_rb_y > GetY() ? @img_rb_y : GetY()));
984 985 SetX(@original_l_margin);
985 986 Cell(0, 0, '', 'T', 0, 'C');
986 987 end
987 988
988 989 #restore position
989 990 SetXY(@original_l_margin, @t_margin);
990 991 end
991 992 end
992 993 alias_method :header, :Header
993 994
994 995 #
995 996 # This method is used to render the page footer.
996 997 # It is automatically called by AddPage() and could be overwritten in your own inherited class.
997 998 #
998 999 def Footer()
999 1000 if (@print_footer)
1000 1001
1001 1002 if (@original_l_margin.nil?)
1002 1003 @original_l_margin = @l_margin;
1003 1004 end
1004 1005 if (@original_r_margin.nil?)
1005 1006 @original_r_margin = @r_margin;
1006 1007 end
1007 1008
1008 1009 #set font
1009 1010 SetFont(@footer_font[0], @footer_font[1] , @footer_font[2]);
1010 1011 #set style for cell border
1011 1012 line_width = 0.3;
1012 1013 SetLineWidth(line_width);
1013 1014 SetDrawColor(0, 0, 0);
1014 1015
1015 1016 footer_height = ((@@k_cell_height_ratio * @footer_font[2]) / @k).round; #footer height, was , 2)
1016 1017 #get footer y position
1017 1018 footer_y = @h - @footer_margin - footer_height;
1018 1019 #set current position
1019 1020 SetXY(@original_l_margin, footer_y);
1020 1021
1021 1022 #print document barcode
1022 1023 if (@barcode)
1023 1024 Ln();
1024 1025 barcode_width = ((@w - @original_l_margin - @original_r_margin)).round; #max width
1025 1026 writeBarcode(@original_l_margin, footer_y + line_width, barcode_width, footer_height - line_width, "C128B", false, false, 2, @barcode);
1026 1027 end
1027 1028
1028 1029 SetXY(@original_l_margin, footer_y);
1029 1030
1030 1031 #Print page number
1031 1032 Cell(0, footer_height, @l['w_page'] + " " + PageNo().to_s + ' / {nb}', 'T', 0, 'R');
1032 1033 end
1033 1034 end
1034 1035 alias_method :footer, :Footer
1035 1036
1036 1037 #
1037 1038 # Returns the current page number.
1038 1039 # @return int page number
1039 1040 # @since 1.0
1040 1041 # @see alias_nb_pages()
1041 1042 #
1042 1043 def PageNo()
1043 1044 #Get current page number
1044 1045 return @page;
1045 1046 end
1046 1047 alias_method :page_no, :PageNo
1047 1048
1048 1049 #
1049 1050 # Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1050 1051 # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1051 1052 # @param int :g Green component (between 0 and 255)
1052 1053 # @param int :b Blue component (between 0 and 255)
1053 1054 # @since 1.3
1054 1055 # @see SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
1055 1056 #
1056 1057 def SetDrawColor(r, g=-1, b=-1)
1057 1058 #Set color for all stroking operations
1058 1059 if ((r==0 and g==0 and b==0) or g==-1)
1059 1060 @draw_color=sprintf('%.3f G', r/255.0);
1060 1061 else
1061 1062 @draw_color=sprintf('%.3f %.3f %.3f RG', r/255.0, g/255.0, b/255.0);
1062 1063 end
1063 1064 if (@page>0)
1064 1065 out(@draw_color);
1065 1066 end
1066 1067 end
1067 1068 alias_method :set_draw_color, :SetDrawColor
1068 1069
1069 1070 #
1070 1071 # Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1071 1072 # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1072 1073 # @param int :g Green component (between 0 and 255)
1073 1074 # @param int :b Blue component (between 0 and 255)
1074 1075 # @param boolean :storeprev if true stores the RGB array on :prevfill_color variable.
1075 1076 # @since 1.3
1076 1077 # @see SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
1077 1078 #
1078 1079 def SetFillColor(r, g=-1, b=-1, storeprev=false)
1079 1080 #Set color for all filling operations
1080 1081 if ((r==0 and g==0 and b==0) or g==-1)
1081 1082 @fill_color=sprintf('%.3f g', r/255.0);
1082 1083 else
1083 1084 @fill_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0);
1084 1085 end
1085 1086 @color_flag=(@fill_color!=@text_color);
1086 1087 if (@page>0)
1087 1088 out(@fill_color);
1088 1089 end
1089 1090 if (storeprev)
1090 1091 # store color as previous value
1091 1092 @prevfill_color = [r, g, b]
1092 1093 end
1093 1094 end
1094 1095 alias_method :set_fill_color, :SetFillColor
1095 1096
1096 1097 # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors
1097 1098 def SetCmykFillColor(c, m, y, k, storeprev=false)
1098 1099 #Set color for all filling operations
1099 1100 @fill_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k);
1100 1101 @color_flag=(@fill_color!=@text_color);
1101 1102 if (storeprev)
1102 1103 # store color as previous value
1103 1104 @prevtext_color = [c, m, y, k]
1104 1105 end
1105 1106 if (@page>0)
1106 1107 out(@fill_color);
1107 1108 end
1108 1109 end
1109 1110 alias_method :set_cmyk_fill_color, :SetCmykFillColor
1110 1111
1111 1112 #
1112 1113 # Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1113 1114 # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1114 1115 # @param int :g Green component (between 0 and 255)
1115 1116 # @param int :b Blue component (between 0 and 255)
1116 1117 # @param boolean :storeprev if true stores the RGB array on :prevtext_color variable.
1117 1118 # @since 1.3
1118 1119 # @see SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
1119 1120 #
1120 1121 def SetTextColor(r, g=-1, b=-1, storeprev=false)
1121 1122 #Set color for text
1122 1123 if ((r==0 and :g==0 and :b==0) or :g==-1)
1123 1124 @text_color=sprintf('%.3f g', r/255.0);
1124 1125 else
1125 1126 @text_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0);
1126 1127 end
1127 1128 @color_flag=(@fill_color!=@text_color);
1128 1129 if (storeprev)
1129 1130 # store color as previous value
1130 1131 @prevtext_color = [r, g, b]
1131 1132 end
1132 1133 end
1133 1134 alias_method :set_text_color, :SetTextColor
1134 1135
1135 1136 # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors
1136 1137 def SetCmykTextColor(c, m, y, k, storeprev=false)
1137 1138 #Set color for text
1138 1139 @text_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k);
1139 1140 @color_flag=(@fill_color!=@text_color);
1140 1141 if (storeprev)
1141 1142 # store color as previous value
1142 1143 @prevtext_color = [c, m, y, k]
1143 1144 end
1144 1145 end
1145 1146 alias_method :set_cmyk_text_color, :SetCmykTextColor
1146 1147
1147 1148 #
1148 1149 # Returns the length of a string in user unit. A font must be selected.<br>
1149 1150 # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02]
1150 1151 # @param string :s The string whose length is to be computed
1151 1152 # @return int
1152 1153 # @since 1.2
1153 1154 #
1154 1155 def GetStringWidth(s)
1155 1156 #Get width of a string in the current font
1156 1157 s = s.to_s;
1157 1158 cw = @current_font['cw']
1158 1159 w = 0;
1159 1160 if (@is_unicode)
1160 1161 unicode = UTF8StringToArray(s);
1161 1162 unicode.each do |char|
1162 1163 if (!cw[char].nil?)
1163 1164 w += cw[char];
1164 1165 # This should not happen. UTF8StringToArray should guarentee the array is ascii values.
1165 1166 # elsif (c!cw[char[0]].nil?)
1166 1167 # w += cw[char[0]];
1167 1168 # elsif (!cw[char.chr].nil?)
1168 1169 # w += cw[char.chr];
1169 1170 elsif (!@current_font['desc']['MissingWidth'].nil?)
1170 1171 w += @current_font['desc']['MissingWidth']; # set default size
1171 1172 else
1172 1173 w += 500;
1173 1174 end
1174 1175 end
1175 1176 else
1176 1177 s.each_byte do |c|
1177 1178 if cw[c.chr]
1178 1179 w += cw[c.chr];
1179 1180 elsif cw[?c.chr]
1180 1181 w += cw[?c.chr]
1181 1182 end
1182 1183 end
1183 1184 end
1184 1185 return (w * @font_size / 1000.0);
1185 1186 end
1186 1187 alias_method :get_string_width, :GetStringWidth
1187 1188
1188 1189 #
1189 1190 # Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page.
1190 1191 # @param float :width The width.
1191 1192 # @since 1.0
1192 1193 # @see Line(), Rect(), Cell(), MultiCell()
1193 1194 #
1194 1195 def SetLineWidth(width)
1195 1196 #Set line width
1196 1197 @line_width = width;
1197 1198 if (@page>0)
1198 1199 out(sprintf('%.2f w', width*@k));
1199 1200 end
1200 1201 end
1201 1202 alias_method :set_line_width, :SetLineWidth
1202 1203
1203 1204 #
1204 1205 # Draws a line between two points.
1205 1206 # @param float :x1 Abscissa of first point
1206 1207 # @param float :y1 Ordinate of first point
1207 1208 # @param float :x2 Abscissa of second point
1208 1209 # @param float :y2 Ordinate of second point
1209 1210 # @since 1.0
1210 1211 # @see SetLineWidth(), SetDrawColor()
1211 1212 #
1212 1213 def Line(x1, y1, x2, y2)
1213 1214 #Draw a line
1214 1215 out(sprintf('%.2f %.2f m %.2f %.2f l S', x1 * @k, (@h - y1) * @k, x2 * @k, (@h - y2) * @k));
1215 1216 end
1216 1217 alias_method :line, :Line
1217 1218
1218 1219 def Circle(mid_x, mid_y, radius, style='')
1219 1220 mid_y = (@h-mid_y)*@k
1220 1221 out(sprintf("q\n")) # postscript content in pdf
1221 1222 # init line type etc. with /GSD gs G g (grey) RG rg (RGB) w=line witdh etc.
1222 1223 out(sprintf("1 j\n")) # line join
1223 1224 # translate ("move") circle to mid_y, mid_y
1224 1225 out(sprintf("1 0 0 1 %f %f cm", mid_x, mid_y))
1225 1226 kappa = 0.5522847498307933984022516322796
1226 1227 # Quadrant 1
1227 1228 x_s = 0.0 # 12 o'clock
1228 1229 y_s = 0.0 + radius
1229 1230 x_e = 0.0 + radius # 3 o'clock
1230 1231 y_e = 0.0
1231 1232 out(sprintf("%f %f m\n", x_s, y_s)) # move to 12 o'clock
1232 1233 # cubic bezier control point 1, start height and kappa * radius to the right
1233 1234 bx_e1 = x_s + (radius * kappa)
1234 1235 by_e1 = y_s
1235 1236 # cubic bezier control point 2, end and kappa * radius above
1236 1237 bx_e2 = x_e
1237 1238 by_e2 = y_e + (radius * kappa)
1238 1239 # draw cubic bezier from current point to x_e/y_e with bx_e1/by_e1 and bx_e2/by_e2 as bezier control points
1239 1240 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1240 1241 # Quadrant 2
1241 1242 x_s = x_e
1242 1243 y_s = y_e # 3 o'clock
1243 1244 x_e = 0.0
1244 1245 y_e = 0.0 - radius # 6 o'clock
1245 1246 bx_e1 = x_s # cubic bezier point 1
1246 1247 by_e1 = y_s - (radius * kappa)
1247 1248 bx_e2 = x_e + (radius * kappa) # cubic bezier point 2
1248 1249 by_e2 = y_e
1249 1250 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1250 1251 # Quadrant 3
1251 1252 x_s = x_e
1252 1253 y_s = y_e # 6 o'clock
1253 1254 x_e = 0.0 - radius
1254 1255 y_e = 0.0 # 9 o'clock
1255 1256 bx_e1 = x_s - (radius * kappa) # cubic bezier point 1
1256 1257 by_e1 = y_s
1257 1258 bx_e2 = x_e # cubic bezier point 2
1258 1259 by_e2 = y_e - (radius * kappa)
1259 1260 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1260 1261 # Quadrant 4
1261 1262 x_s = x_e
1262 1263 y_s = y_e # 9 o'clock
1263 1264 x_e = 0.0
1264 1265 y_e = 0.0 + radius # 12 o'clock
1265 1266 bx_e1 = x_s # cubic bezier point 1
1266 1267 by_e1 = y_s + (radius * kappa)
1267 1268 bx_e2 = x_e - (radius * kappa) # cubic bezier point 2
1268 1269 by_e2 = y_e
1269 1270 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1270 1271 if style=='F'
1271 1272 op='f'
1272 1273 elsif style=='FD' or style=='DF'
1273 1274 op='b'
1274 1275 else
1275 1276 op='s'
1276 1277 end
1277 1278 out(sprintf("#{op}\n")) # stroke circle, do not fill and close path
1278 1279 # for filling etc. b, b*, f, f*
1279 1280 out(sprintf("Q\n")) # finish postscript in PDF
1280 1281 end
1281 1282 alias_method :circle, :Circle
1282 1283
1283 1284 #
1284 1285 # Outputs a rectangle. It can be drawn (border only), filled (with no border) or both.
1285 1286 # @param float :x Abscissa of upper-left corner
1286 1287 # @param float :y Ordinate of upper-left corner
1287 1288 # @param float :w Width
1288 1289 # @param float :h Height
1289 1290 # @param string :style Style of rendering. Possible values are:<ul><li>D or empty string: draw (default)</li><li>F: fill</li><li>DF or FD: draw and fill</li></ul>
1290 1291 # @since 1.0
1291 1292 # @see SetLineWidth(), SetDrawColor(), SetFillColor()
1292 1293 #
1293 1294 def Rect(x, y, w, h, style='')
1294 1295 #Draw a rectangle
1295 1296 if (style=='F')
1296 1297 op='f';
1297 1298 elsif (style=='FD' or style=='DF')
1298 1299 op='B';
1299 1300 else
1300 1301 op='S';
1301 1302 end
1302 1303 out(sprintf('%.2f %.2f %.2f %.2f re %s', x * @k, (@h - y) * @k, w * @k, -h * @k, op));
1303 1304 end
1304 1305 alias_method :rect, :Rect
1305 1306
1306 1307 #
1307 1308 # Imports a TrueType or Type1 font and makes it available. It is necessary to generate a font definition file first with the makefont.rb utility. The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by FPDF_FONTPATH if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
1308 1309 # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02].
1309 1310 # <b>Example</b>:<br />
1310 1311 # <pre>
1311 1312 # :pdf->AddFont('Comic','I');
1312 1313 # # is equivalent to:
1313 1314 # :pdf->AddFont('Comic','I','comici.rb');
1314 1315 # </pre>
1315 1316 # @param string :family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
1316 1317 # @param string :style Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
1317 1318 # @param string :file The font definition file. By default, the name is built from the family and style, in lower case with no space.
1318 1319 # @since 1.5
1319 1320 # @see SetFont()
1320 1321 #
1321 1322 def AddFont(family, style='', file='')
1322 1323 if (family.empty?)
1323 1324 return;
1324 1325 end
1325 1326
1326 1327 #Add a TrueType or Type1 font
1327 1328 family = family.downcase
1328 1329 if ((!@is_unicode) and (family == 'arial'))
1329 1330 family = 'helvetica';
1330 1331 end
1331 1332
1332 1333 style=style.upcase
1333 1334 style=style.gsub('U','');
1334 1335 style=style.gsub('D','');
1335 1336 if (style == 'IB')
1336 1337 style = 'BI';
1337 1338 end
1338 1339
1339 1340 fontkey = family + style;
1340 1341 # check if the font has been already added
1341 1342 if !@fonts[fontkey].nil?
1342 1343 return;
1343 1344 end
1344 1345
1345 1346 if (file=='')
1346 1347 file = family.gsub(' ', '') + style.downcase + '.rb';
1347 1348 end
1348 1349 font_file_name = getfontpath(file)
1349 1350 if (font_file_name.nil?)
1350 1351 # try to load the basic file without styles
1351 1352 file = family.gsub(' ', '') + '.rb';
1352 1353 font_file_name = getfontpath(file)
1353 1354 end
1354 1355 if font_file_name.nil?
1355 1356 Error("Could not find font #{file}.")
1356 1357 end
1357 1358 require(getfontpath(file))
1358 1359 font_desc = TCPDFFontDescriptor.font(file)
1359 1360
1360 1361 if (font_desc[:name].nil? and @@fpdf_charwidths.nil?)
1361 1362 Error('Could not include font definition file');
1362 1363 end
1363 1364
1364 1365 i = @fonts.length+1;
1365 1366 if (@is_unicode)
1366 1367 @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg], 'cMap' => font_desc[:cMap], 'registry' => font_desc[:registry]}
1367 1368 @@fpdf_charwidths[fontkey] = font_desc[:cw];
1368 1369 else
1369 1370 @fonts[fontkey]={'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]}
1370 1371 @@fpdf_charwidths[fontkey] = font_desc[:cw];
1371 1372 end
1372 1373
1373 1374 if (!font_desc[:diff].nil? and (!font_desc[:diff].empty?))
1374 1375 #Search existing encodings
1375 1376 d=0;
1376 1377 nb=@diffs.length;
1377 1378 1.upto(nb) do |i|
1378 1379 if (@diffs[i]== font_desc[:diff])
1379 1380 d = i;
1380 1381 break;
1381 1382 end
1382 1383 end
1383 1384 if (d==0)
1384 1385 d = nb+1;
1385 1386 @diffs[d] = font_desc[:diff];
1386 1387 end
1387 1388 @fonts[fontkey]['diff'] = d;
1388 1389 end
1389 1390 if (font_desc[:file] and font_desc[:file].length > 0)
1390 1391 if (font_desc[:type] == "TrueType") or (font_desc[:type] == "TrueTypeUnicode")
1391 1392 @font_files[font_desc[:file]] = {'length1' => font_desc[:originalsize]}
1392 1393 else
1393 1394 @font_files[font_desc[:file]] = {'length1' => font_desc[:size1], 'length2' => font_desc[:size2]}
1394 1395 end
1395 1396 end
1396 1397 end
1397 1398 alias_method :add_font, :AddFont
1398 1399
1399 1400 #
1400 1401 # Sets the font used to print character strings. It is mandatory to call this method at least once before printing text or the resulting document would not be valid.
1401 1402 # The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
1402 1403 # The method can be called before the first page is created and the font is retained from page to page.
1403 1404 # If you just wish to change the current font size, it is simpler to call SetFontSize().
1404 1405 # Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the FPDF_FONTPATH constant</li></ul><br />
1405 1406 # Example for the last case (note the trailing slash):<br />
1406 1407 # <pre>
1407 1408 # define('FPDF_FONTPATH','/home/www/font/');
1408 1409 # require('tcpdf.rb');
1409 1410 #
1410 1411 # #Times regular 12
1411 1412 # :pdf->SetFont('Times');
1412 1413 # #Arial bold 14
1413 1414 # :pdf->SetFont('Arial','B',14);
1414 1415 # #Removes bold
1415 1416 # :pdf->SetFont('');
1416 1417 # #Times bold, italic and underlined 14
1417 1418 # :pdf->SetFont('Times','BIUD');
1418 1419 # </pre><br />
1419 1420 # If the file corresponding to the requested font is not found, the error "Could not include font metric file" is generated.
1420 1421 # @param string :family Family font. It can be either a name defined by AddFont() or one of the standard families (case insensitive):<ul><li>Courier (fixed-width)</li><li>Helvetica or Arial (synonymous; sans serif)</li><li>Times (serif)</li><li>Symbol (symbolic)</li><li>ZapfDingbats (symbolic)</li></ul>It is also possible to pass an empty string. In that case, the current family is retained.
1421 1422 # @param string :style Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li></ul>or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats
1422 1423 # @param float :size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
1423 1424 # @since 1.0
1424 1425 # @see AddFont(), SetFontSize(), Cell(), MultiCell(), Write()
1425 1426 #
1426 1427 def SetFont(family, style='', size=0)
1427 1428 # save previous values
1428 1429 @prevfont_family = @font_family;
1429 1430 @prevfont_style = @font_style;
1430 1431
1431 1432 family=family.downcase;
1432 1433 if (family=='')
1433 1434 family=@font_family;
1434 1435 end
1435 1436 if ((!@is_unicode) and (family == 'arial'))
1436 1437 family = 'helvetica';
1437 1438 elsif ((family=="symbol") or (family=="zapfdingbats"))
1438 1439 style='';
1439 1440 end
1440 1441
1441 1442 style=style.upcase;
1442 1443
1443 1444 if (style.include?('U'))
1444 1445 @underline=true;
1445 1446 style= style.gsub('U','');
1446 1447 else
1447 1448 @underline=false;
1448 1449 end
1449 1450 if (style.include?('D'))
1450 1451 @deleted=true;
1451 1452 style= style.gsub('D','');
1452 1453 else
1453 1454 @deleted=false;
1454 1455 end
1455 1456 if (style=='IB')
1456 1457 style='BI';
1457 1458 end
1458 1459 if (size==0)
1459 1460 size=@font_size_pt;
1460 1461 end
1461 1462
1462 1463 # try to add font (if not already added)
1463 1464 AddFont(family, style);
1464 1465
1465 1466 #Test if font is already selected
1466 1467 if ((@font_family == family) and (@font_style == style) and (@font_size_pt == size))
1467 1468 return;
1468 1469 end
1469 1470
1470 1471 fontkey = family + style;
1471 1472 style = '' if (@fonts[fontkey].nil? and !@fonts[family].nil?)
1472 1473
1473 1474 #Test if used for the first time
1474 1475 if (@fonts[fontkey].nil?)
1475 1476 #Check if one of the standard fonts
1476 1477 if (!@core_fonts[fontkey].nil?)
1477 1478 if @@fpdf_charwidths[fontkey].nil?
1478 1479 #Load metric file
1479 1480 file = family;
1480 1481 if ((family!='symbol') and (family!='zapfdingbats'))
1481 1482 file += style.downcase;
1482 1483 end
1483 1484 if (getfontpath(file + '.rb').nil?)
1484 1485 # try to load the basic file without styles
1485 1486 file = family;
1486 1487 fontkey = family;
1487 1488 end
1488 1489 require(getfontpath(file + '.rb'));
1489 1490 font_desc = TCPDFFontDescriptor.font(file)
1490 1491 if ((@is_unicode and ctg.nil?) or ((!@is_unicode) and (@@fpdf_charwidths[fontkey].nil?)) )
1491 1492 Error("Could not include font metric file [" + fontkey + "]: " + getfontpath(file + ".rb"));
1492 1493 end
1493 1494 end
1494 1495 i = @fonts.length + 1;
1495 1496
1496 1497 if (@is_unicode)
1497 1498 @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg]}
1498 1499 @@fpdf_charwidths[fontkey] = font_desc[:cw];
1499 1500 else
1500 1501 @fonts[fontkey] = {'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]}
1501 1502 @@fpdf_charwidths[fontkey] = font_desc[:cw];
1502 1503 end
1503 1504 else
1504 1505 Error('Undefined font: ' + family + ' ' + style);
1505 1506 end
1506 1507 end
1507 1508 #Select it
1508 1509 @font_family = family;
1509 1510 @font_style = style;
1510 1511 @font_size_pt = size;
1511 1512 @font_size = size / @k;
1512 1513 @current_font = @fonts[fontkey]; # was & may need deep copy?
1513 1514 if (@page>0)
1514 1515 out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt));
1515 1516 end
1516 1517 end
1517 1518 alias_method :set_font, :SetFont
1518 1519
1519 1520 #
1520 1521 # Defines the size of the current font.
1521 1522 # @param float :size The size (in points)
1522 1523 # @since 1.0
1523 1524 # @see SetFont()
1524 1525 #
1525 1526 def SetFontSize(size)
1526 1527 #Set font size in points
1527 1528 if (@font_size_pt== size)
1528 1529 return;
1529 1530 end
1530 1531 @font_size_pt = size;
1531 1532 @font_size = size.to_f / @k;
1532 1533 if (@page > 0)
1533 1534 out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt));
1534 1535 end
1535 1536 end
1536 1537 alias_method :set_font_size, :SetFontSize
1537 1538
1538 1539 #
1539 1540 # Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
1540 1541 # The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
1541 1542 # @since 1.5
1542 1543 # @see Cell(), Write(), Image(), Link(), SetLink()
1543 1544 #
1544 1545 def AddLink()
1545 1546 #Create a new internal link
1546 1547 n=@links.length+1;
1547 1548 @links[n]=[0,0];
1548 1549 return n;
1549 1550 end
1550 1551 alias_method :add_link, :AddLink
1551 1552
1552 1553 #
1553 1554 # Defines the page and position a link points to
1554 1555 # @param int :link The link identifier returned by AddLink()
1555 1556 # @param float :y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
1556 1557 # @param int :page Number of target page; -1 indicates the current page. This is the default value
1557 1558 # @since 1.5
1558 1559 # @see AddLink()
1559 1560 #
1560 1561 def SetLink(link, y=0, page=-1)
1561 1562 #Set destination of internal link
1562 1563 if (y==-1)
1563 1564 y=@y;
1564 1565 end
1565 1566 if (page==-1)
1566 1567 page=@page;
1567 1568 end
1568 1569 @links[link] = [page, y]
1569 1570 end
1570 1571 alias_method :set_link, :SetLink
1571 1572
1572 1573 #
1573 1574 # Puts a link on a rectangular area of the page. Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
1574 1575 # @param float :x Abscissa of the upper-left corner of the rectangle
1575 1576 # @param float :y Ordinate of the upper-left corner of the rectangle
1576 1577 # @param float :w Width of the rectangle
1577 1578 # @param float :h Height of the rectangle
1578 1579 # @param mixed :link URL or identifier returned by AddLink()
1579 1580 # @since 1.5
1580 1581 # @see AddLink(), Cell(), Write(), Image()
1581 1582 #
1582 1583 def Link(x, y, w, h, link)
1583 1584 #Put a link on the page
1584 1585 @page_links ||= Array.new
1585 1586 @page_links[@page] ||= Array.new
1586 1587 @page_links[@page].push([x * @k, @h_pt - y * @k, w * @k, h*@k, link]);
1587 1588 end
1588 1589 alias_method :link, :Link
1589 1590
1590 1591 #
1591 1592 # Prints a character string. The origin is on the left of the first charcter, on the baseline. This method allows to place a string precisely on the page, but it is usually easier to use Cell(), MultiCell() or Write() which are the standard methods to print text.
1592 1593 # @param float :x Abscissa of the origin
1593 1594 # @param float :y Ordinate of the origin
1594 1595 # @param string :txt String to print
1595 1596 # @since 1.0
1596 1597 # @see SetFont(), SetTextColor(), Cell(), MultiCell(), Write()
1597 1598 #
1598 1599 def Text(x, y, txt)
1599 1600 #Output a string
1600 1601 s=sprintf('BT %.2f %.2f Td (%s) Tj ET', x * @k, (@h-y) * @k, escapetext(txt));
1601 1602 if (@underline and (txt!=''))
1602 1603 s += ' ' + dolinetxt(x, y, txt);
1603 1604 end
1604 1605 if (@color_flag)
1605 1606 s='q ' + @text_color + ' ' + s + ' Q';
1606 1607 end
1607 1608 out(s);
1608 1609 end
1609 1610 alias_method :text, :Text
1610 1611
1611 1612 #
1612 1613 # Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value. The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
1613 1614 # This method is called automatically and should not be called directly by the application.<br />
1614 1615 # <b>Example:</b><br />
1615 1616 # The method is overriden in an inherited class in order to obtain a 3 column layout:<br />
1616 1617 # <pre>
1617 1618 # class PDF extends TCPDF {
1618 1619 # var :col=0;
1619 1620 #
1620 1621 # def SetCol(col)
1621 1622 # #Move position to a column
1622 1623 # @col = col;
1623 1624 # :x=10+:col*65;
1624 1625 # SetLeftMargin(x);
1625 1626 # SetX(x);
1626 1627 # end
1627 1628 #
1628 1629 # def AcceptPageBreak()
1629 1630 # if (@col<2)
1630 1631 # #Go to next column
1631 1632 # SetCol(@col+1);
1632 1633 # SetY(10);
1633 1634 # return false;
1634 1635 # end
1635 1636 # else
1636 1637 # #Go back to first column and issue page break
1637 1638 # SetCol(0);
1638 1639 # return true;
1639 1640 # end
1640 1641 # end
1641 1642 # }
1642 1643 #
1643 1644 # :pdf=new PDF();
1644 1645 # :pdf->Open();
1645 1646 # :pdf->AddPage();
1646 1647 # :pdf->SetFont('Arial','',12);
1647 1648 # for(i=1;:i<=300;:i++)
1648 1649 # :pdf->Cell(0,5,"Line :i",0,1);
1649 1650 # }
1650 1651 # :pdf->Output();
1651 1652 # </pre>
1652 1653 # @return boolean
1653 1654 # @since 1.4
1654 1655 # @see SetAutoPageBreak()
1655 1656 #
1656 1657 def AcceptPageBreak()
1657 1658 #Accept automatic page break or not
1658 1659 return @auto_page_break;
1659 1660 end
1660 1661 alias_method :accept_page_break, :AcceptPageBreak
1661 1662
1662 1663 def BreakThePage?(h)
1663 1664 if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak())
1664 1665 true
1665 1666 else
1666 1667 false
1667 1668 end
1668 1669 end
1669 1670 alias_method :break_the_page?, :BreakThePage?
1670 1671 #
1671 1672 # Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
1672 1673 # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
1673 1674 # @param float :w Cell width. If 0, the cell extends up to the right margin.
1674 1675 # @param float :h Cell height. Default value: 0.
1675 1676 # @param string :txt String to print. Default value: empty string.
1676 1677 # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
1677 1678 # @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
1678 1679 # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
1679 1680 # @param string :align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li></ul>
1680 1681 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
1681 1682 # @param mixed :link URL or identifier returned by AddLink().
1682 1683 # @since 1.0
1683 1684 # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
1684 1685 #
1685 1686 def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil)
1686 1687 #Output a cell
1687 1688 k=@k;
1688 1689 if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak())
1689 1690 #Automatic page break
1690 1691 if @pages[@page+1].nil?
1691 1692 x = @x;
1692 1693 ws = @ws;
1693 1694 if (ws > 0)
1694 1695 @ws = 0;
1695 1696 out('0 Tw');
1696 1697 end
1697 1698 AddPage(@cur_orientation);
1698 1699 @x = x;
1699 1700 if (ws > 0)
1700 1701 @ws = ws;
1701 1702 out(sprintf('%.3f Tw', ws * k));
1702 1703 end
1703 1704 else
1704 1705 @page += 1;
1705 1706 @y=@t_margin;
1706 1707 end
1707 1708 end
1708 1709
1709 1710 if (w == 0)
1710 1711 w = @w - @r_margin - @x;
1711 1712 end
1712 1713 s = '';
1713 1714 if ((fill.to_i == 1) or (border.to_i == 1))
1714 1715 if (fill.to_i == 1)
1715 1716 op = (border.to_i == 1) ? 'B' : 'f';
1716 1717 else
1717 1718 op = 'S';
1718 1719 end
1719 1720 s = sprintf('%.2f %.2f %.2f %.2f re %s ', @x * k, (@h - @y) * k, w * k, -h * k, op);
1720 1721 end
1721 1722 if (border.is_a?(String))
1722 1723 x=@x;
1723 1724 y=@y;
1724 1725 if (border.include?('L'))
1725 1726 s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-y)*k, x*k,(@h-(y+h))*k);
1726 1727 end
1727 1728 if (border.include?('T'))
1728 1729 s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-y)*k,(x+w)*k,(@h-y)*k);
1729 1730 end
1730 1731 if (border.include?('R'))
1731 1732 s<<sprintf('%.2f %.2f m %.2f %.2f l S ',(x+w)*k,(@h-y)*k,(x+w)*k,(@h-(y+h))*k);
1732 1733 end
1733 1734 if (border.include?('B'))
1734 1735 s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-(y+h))*k,(x+w)*k,(@h-(y+h))*k);
1735 1736 end
1736 1737 end
1737 1738 if (txt != '')
1738 1739 width = GetStringWidth(txt);
1739 1740 if (align == 'R' || align == 'right')
1740 1741 dx = w - @c_margin - width;
1741 1742 elsif (align=='C' || align == 'center')
1742 1743 dx = (w - width)/2;
1743 1744 else
1744 1745 dx = @c_margin;
1745 1746 end
1746 1747 if (@color_flag)
1747 1748 s << 'q ' + @text_color + ' ';
1748 1749 end
1749 1750 txt2 = escapetext(txt);
1750 1751 s<<sprintf('BT %.2f %.2f Td (%s) Tj ET', (@x + dx) * k, (@h - (@y + 0.5 * h + 0.3 * @font_size)) * k, txt2);
1751 1752 if (@underline)
1752 1753 s<<' ' + dolinetxt(@x + dx, @y + 0.5 * h + 0.3 * @font_size, txt);
1753 1754 end
1754 1755 if (@deleted)
1755 1756 s<<' ' + dolinetxt(@x + dx, @y + 0.3 * h + 0.2 * @font_size, txt);
1756 1757 end
1757 1758 if (@color_flag)
1758 1759 s<<' Q';
1759 1760 end
1760 1761 if link && !link.empty?
1761 1762 Link(@x + dx, @y + 0.5 * h - 0.5 * @font_size, width, @font_size, link);
1762 1763 end
1763 1764 end
1764 1765 if (s)
1765 1766 out(s);
1766 1767 end
1767 1768 @lasth = h;
1768 1769 if (ln.to_i>0)
1769 1770 # Go to next line
1770 1771 @y += h;
1771 1772 if (ln == 1)
1772 1773 @x = @l_margin;
1773 1774 end
1774 1775 else
1775 1776 @x += w;
1776 1777 end
1777 1778 end
1778 1779 alias_method :cell, :Cell
1779 1780
1780 1781 #
1781 1782 # This method allows printing text with line breaks. They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
1782 1783 # Text can be aligned, centered or justified. The cell block can be framed and the background painted.
1783 1784 # @param float :w Width of cells. If 0, they extend up to the right margin of the page.
1784 1785 # @param float :h Height of cells.
1785 1786 # @param string :txt String to print
1786 1787 # @param mixed :border Indicates if borders must be drawn around the cell block. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
1787 1788 # @param string :align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value)</li></ul>
1788 1789 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
1789 1790 # @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
1790 1791 # @since 1.3
1791 1792 # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
1792 1793 #
1793 1794 def MultiCell(w, h, txt, border=0, align='J', fill=0, ln=1)
1794 1795
1795 1796 # save current position
1796 1797 prevx = @x;
1797 1798 prevy = @y;
1798 1799 prevpage = @page;
1799 1800
1800 1801 #Output text with automatic or explicit line breaks
1801 1802
1802 1803 if (w == 0)
1803 1804 w = @w - @r_margin - @x;
1804 1805 end
1805 1806
1806 1807 wmax = (w - 3 * @c_margin);
1807 1808
1808 1809 s = txt.gsub("\r", ''); # remove carriage returns
1809 1810 nb = s.length;
1810 1811
1811 1812 b=0;
1812 1813 if (border)
1813 1814 if (border==1)
1814 1815 border='LTRB';
1815 1816 b='LRT';
1816 1817 b2='LR';
1817 1818 elsif border.is_a?(String)
1818 1819 b2='';
1819 1820 if (border.include?('L'))
1820 1821 b2<<'L';
1821 1822 end
1822 1823 if (border.include?('R'))
1823 1824 b2<<'R';
1824 1825 end
1825 1826 b=(border.include?('T')) ? b2 + 'T' : b2;
1826 1827 end
1827 1828 end
1828 1829 sep=-1;
1829 1830 to_index=0;
1830 1831 from_j=0;
1831 1832 l=0;
1832 1833 ns=0;
1833 1834 nl=1;
1834 1835
1835 1836 while to_index < nb
1836 1837 #Get next character
1837 1838 c = s[to_index];
1838 1839 if c == "\n"[0]
1839 1840 #Explicit line break
1840 1841 if @ws > 0
1841 1842 @ws = 0
1842 1843 out('0 Tw')
1843 1844 end
1844 1845 #Ed Moss - change begin
1845 1846 end_i = to_index == 0 ? 0 : to_index - 1
1846 1847 # Changed from s[from_j..to_index] to fix bug reported by Hans Allis.
1847 1848 from_j = to_index == 0 ? 1 : from_j
1848 1849 Cell(w, h, s[from_j..end_i], b, 2, align, fill)
1849 1850 #change end
1850 1851 to_index += 1
1851 1852 sep=-1
1852 1853 from_j=to_index
1853 1854 l=0
1854 1855 ns=0
1855 1856 nl += 1
1856 1857 b = b2 if border and nl==2
1857 1858 next
1858 1859 end
1859 1860 if (c == " "[0])
1860 1861 sep = to_index;
1861 1862 ls = l;
1862 1863 ns += 1;
1863 1864 end
1864 1865
1865 1866 l = GetStringWidth(s[from_j, to_index - from_j]);
1866 1867
1867 1868 if (l > wmax)
1868 1869 #Automatic line break
1869 1870 if (sep == -1)
1870 1871 if (to_index == from_j)
1871 1872 to_index += 1;
1872 1873 end
1873 1874 if (@ws > 0)
1874 1875 @ws = 0;
1875 1876 out('0 Tw');
1876 1877 end
1877 1878 Cell(w, h, s[from_j..to_index-1], b, 2, align, fill) # my FPDF version
1878 1879 else
1879 1880 if (align=='J' || align=='justify' || align=='justified')
1880 1881 @ws = (ns>1) ? (wmax-ls)/(ns-1) : 0;
1881 1882 out(sprintf('%.3f Tw', @ws * @k));
1882 1883 end
1883 1884 Cell(w, h, s[from_j..sep], b, 2, align, fill);
1884 1885 to_index = sep + 1;
1885 1886 end
1886 1887 sep=-1;
1887 1888 from_j = to_index;
1888 1889 l=0;
1889 1890 ns=0;
1890 1891 nl += 1;
1891 1892 if (border and (nl==2))
1892 1893 b = b2;
1893 1894 end
1894 1895 else
1895 1896 to_index += 1;
1896 1897 end
1897 1898 end
1898 1899 #Last chunk
1899 1900 if (@ws>0)
1900 1901 @ws=0;
1901 1902 out('0 Tw');
1902 1903 end
1903 1904 if (border.is_a?(String) and border.include?('B'))
1904 1905 b<<'B';
1905 1906 end
1906 1907 Cell(w, h, s[from_j, to_index-from_j], b, 2, align, fill);
1907 1908
1908 1909 # move cursor to specified position
1909 1910 # since 2007-03-03
1910 1911 if (ln == 1)
1911 1912 # go to the beginning of the next line
1912 1913 @x = @l_margin;
1913 1914 elsif (ln == 0)
1914 1915 # go to the top-right of the cell
1915 1916 @page = prevpage;
1916 1917 @y = prevy;
1917 1918 @x = prevx + w;
1918 1919 elsif (ln == 2)
1919 1920 # go to the bottom-left of the cell
1920 1921 @x = prevx;
1921 1922 end
1922 1923 end
1923 1924 alias_method :multi_cell, :MultiCell
1924 1925
1925 1926 #
1926 1927 # This method prints text from the current position. When the right margin is reached (or the \n character is met) a line break occurs and text continues from the left margin. Upon method exit, the current position is left just at the end of the text. It is possible to put a link on the text.<br />
1927 1928 # <b>Example:</b><br />
1928 1929 # <pre>
1929 1930 # #Begin with regular font
1930 1931 # :pdf->SetFont('Arial','',14);
1931 1932 # :pdf->Write(5,'Visit ');
1932 1933 # #Then put a blue underlined link
1933 1934 # :pdf->SetTextColor(0,0,255);
1934 1935 # :pdf->SetFont('','U');
1935 1936 # :pdf->Write(5,'www.tecnick.com','http://www.tecnick.com');
1936 1937 # </pre>
1937 1938 # @param float :h Line height
1938 1939 # @param string :txt String to print
1939 1940 # @param mixed :link URL or identifier returned by AddLink()
1940 1941 # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0.
1941 1942 # @since 1.5
1942 1943 # @see SetFont(), SetTextColor(), AddLink(), MultiCell(), SetAutoPageBreak()
1943 1944 #
1944 1945 def Write(h, txt, link=nil, fill=0)
1945 1946
1946 1947 #Output text in flowing mode
1947 1948 w = @w - @r_margin - @x;
1948 1949 wmax = (w - 3 * @c_margin);
1949 1950
1950 1951 s = txt.gsub("\r", '');
1951 1952 nb = s.length;
1952 1953
1953 1954 # handle single space character
1954 1955 if ((nb==1) and (s == " "))
1955 1956 @x += GetStringWidth(s);
1956 1957 return;
1957 1958 end
1958 1959
1959 1960 sep=-1;
1960 1961 i=0;
1961 1962 j=0;
1962 1963 l=0;
1963 1964 nl=1;
1964 1965 while(i<nb)
1965 1966 #Get next character
1966 1967 c = s[i];
1967 1968 if (c == "\n"[0])
1968 1969 #Explicit line break
1969 1970 Cell(w, h, s[j,i-j], 0, 2, '', fill, link);
1970 1971 i += 1;
1971 1972 sep = -1;
1972 1973 j = i;
1973 1974 l = 0;
1974 1975 if (nl == 1)
1975 1976 @x = @l_margin;
1976 1977 w = @w - @r_margin - @x;
1977 1978 wmax = (w - 3 * @c_margin);
1978 1979 end
1979 1980 nl += 1;
1980 1981 next
1981 1982 end
1982 1983 if (c == " "[0])
1983 1984 sep= i;
1984 1985 end
1985 1986 l = GetStringWidth(s[j, i - j]);
1986 1987 if (l > wmax)
1987 1988 #Automatic line break (word wrapping)
1988 1989 if (sep == -1)
1989 1990 if (@x > @l_margin)
1990 1991 #Move to next line
1991 1992 @x = @l_margin;
1992 1993 @y += h;
1993 1994 w=@w - @r_margin - @x;
1994 1995 wmax=(w - 3 * @c_margin);
1995 1996 i += 1
1996 1997 nl += 1
1997 1998 next
1998 1999 end
1999 2000 if (i == j)
2000 2001 i += 1
2001 2002 end
2002 2003 Cell(w, h, s[j, (i-1)], 0, 2, '', fill, link);
2003 2004 else
2004 2005 Cell(w, h, s[j, (sep-j)], 0, 2, '', fill, link);
2005 2006 i = sep+1;
2006 2007 end
2007 2008 sep = -1;
2008 2009 j = i;
2009 2010 l = 0;
2010 2011 if (nl==1)
2011 2012 @x = @l_margin;
2012 2013 w = @w - @r_margin - @x;
2013 2014 wmax = (w - 3 * @c_margin);
2014 2015 end
2015 2016 nl += 1;
2016 2017 else
2017 2018 i += 1;
2018 2019 end
2019 2020 end
2020 2021 #Last chunk
2021 2022 if (i != j)
2022 2023 Cell(GetStringWidth(s[j..i]), h, s[j..i], 0, 0, '', fill, link);
2023 2024 end
2024 2025 end
2025 2026 alias_method :write, :Write
2026 2027
2027 2028 #
2028 2029 # Puts an image in the page. The upper-left corner must be given. The dimensions can be specified in different ways:<ul><li>explicit width and height (expressed in user unit)</li><li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li><li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
2029 2030 # Supported formats are JPEG and PNG.
2030 2031 # For JPEG, all flavors are allowed:<ul><li>gray scales</li><li>true colors (24 bits)</li><li>CMYK (32 bits)</li></ul>
2031 2032 # For PNG, are allowed:<ul><li>gray scales on at most 8 bits (256 levels)</li><li>indexed colors</li><li>true colors (24 bits)</li></ul>
2032 2033 # but are not supported:<ul><li>Interlacing</li><li>Alpha channel</li></ul>
2033 2034 # If a transparent color is defined, it will be taken into account (but will be only interpreted by Acrobat 4 and above).<br />
2034 2035 # The format can be specified explicitly or inferred from the file extension.<br />
2035 2036 # It is possible to put a link on the image.<br />
2036 2037 # Remark: if an image is used several times, only one copy will be embedded in the file.<br />
2037 2038 # @param string :file Name of the file containing the image.
2038 2039 # @param float :x Abscissa of the upper-left corner.
2039 2040 # @param float :y Ordinate of the upper-left corner.
2040 2041 # @param float :w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
2041 2042 # @param float :h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
2042 2043 # @param string :type Image format. Possible values are (case insensitive): JPG, JPEG, PNG. If not specified, the type is inferred from the file extension.
2043 2044 # @param mixed :link URL or identifier returned by AddLink().
2044 2045 # @since 1.1
2045 2046 # @see AddLink()
2046 2047 #
2047 2048 def Image(file, x, y, w=0, h=0, type='', link=nil)
2048 2049 #Put an image on the page
2049 2050 if (@images[file].nil?)
2050 2051 #First use of image, get info
2051 2052 if (type == '')
2052 2053 pos = File::basename(file).rindex('.');
2053 2054 if (pos.nil? or pos == 0)
2054 2055 Error('Image file has no extension and no type was specified: ' + file);
2055 2056 end
2056 2057 pos = file.rindex('.');
2057 2058 type = file[pos+1..-1];
2058 2059 end
2059 2060 type.downcase!
2060 2061 if (type == 'jpg' or type == 'jpeg')
2061 2062 info=parsejpg(file);
2062 2063 elsif (type == 'png' or type == 'gif')
2063 2064 img = Magick::ImageList.new(file)
2064 2065 img.format = "PNG" # convert to PNG from gif
2065 2066 img.opacity = 0 # PNG alpha channel delete
2066 2067 File.open( @@k_path_cache + File::basename(file), 'w'){|f|
2067 2068 f.binmode
2068 2069 f.print img.to_blob
2069 2070 f.close
2070 2071 }
2071 2072 info=parsepng( @@k_path_cache + File::basename(file));
2072 2073 File.delete( @@k_path_cache + File::basename(file))
2073 2074 else
2074 2075 #Allow for additional formats
2075 2076 mtd='parse' + type;
2076 2077 if (!self.respond_to?(mtd))
2077 2078 Error('Unsupported image type: ' + type);
2078 2079 end
2079 2080 info=send(mtd, file);
2080 2081 end
2081 2082 info['i']=@images.length+1;
2082 2083 @images[file] = info;
2083 2084 else
2084 2085 info=@images[file];
2085 2086 end
2086 2087 #Automatic width and height calculation if needed
2087 2088 if ((w == 0) and (h == 0))
2088 2089 rescale_x = (@w - @r_margin - x) / (info['w'] / (@img_scale * @k))
2089 2090 rescale_x = 1 if rescale_x >= 1
2090 2091 if (y + info['h'] * rescale_x / (@img_scale * @k) > @page_break_trigger and !@in_footer and AcceptPageBreak())
2091 2092 #Automatic page break
2092 2093 if @pages[@page+1].nil?
2093 2094 ws = @ws;
2094 2095 if (ws > 0)
2095 2096 @ws = 0;
2096 2097 out('0 Tw');
2097 2098 end
2098 2099 AddPage(@cur_orientation);
2099 2100 if (ws > 0)
2100 2101 @ws = ws;
2101 2102 out(sprintf('%.3f Tw', ws * @k));
2102 2103 end
2103 2104 else
2104 2105 @page += 1;
2105 2106 end
2106 2107 y=@t_margin;
2107 2108 end
2108 2109 rescale_y = (@page_break_trigger - y) / (info['h'] / (@img_scale * @k))
2109 2110 rescale_y = 1 if rescale_y >= 1
2110 2111 rescale = rescale_y >= rescale_x ? rescale_x : rescale_y
2111 2112
2112 2113 #Put image at 72 dpi
2113 2114 # 2004-06-14 :: Nicola Asuni, scale factor where added
2114 2115 w = info['w'] * rescale / (@img_scale * @k);
2115 2116 h = info['h'] * rescale / (@img_scale * @k);
2116 2117 elsif (w == 0)
2117 2118 w = h * info['w'] / info['h'];
2118 2119 elsif (h == 0)
2119 2120 h = w * info['h'] / info['w'];
2120 2121 end
2121 2122 out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w*@k, h*@k, x*@k, (@h-(y+h))*@k, info['i']));
2122 2123 if (link)
2123 2124 Link(x, y, w, h, link);
2124 2125 end
2125 2126
2126 2127 #2002-07-31 - Nicola Asuni
2127 2128 # set right-bottom corner coordinates
2128 2129 @img_rb_x = x + w;
2129 2130 @img_rb_y = y + h;
2130 2131 end
2131 2132 alias_method :image, :Image
2132 2133
2133 2134 #
2134 2135 # Performs a line break. The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
2135 2136 # @param float :h The height of the break. By default, the value equals the height of the last printed cell.
2136 2137 # @since 1.0
2137 2138 # @see Cell()
2138 2139 #
2139 2140 def Ln(h='')
2140 2141 #Line feed; default value is last cell height
2141 2142 @x=@l_margin;
2142 2143 if (h.is_a?(String))
2143 2144 @y += @lasth;
2144 2145 else
2145 2146 @y += h;
2146 2147 end
2147 2148
2148 2149 k=@k;
2149 2150 if (@y > @page_break_trigger and !@in_footer and AcceptPageBreak())
2150 2151 #Automatic page break
2151 2152 if @pages[@page+1].nil?
2152 2153 x = @x;
2153 2154 ws = @ws;
2154 2155 if (ws > 0)
2155 2156 @ws = 0;
2156 2157 out('0 Tw');
2157 2158 end
2158 2159 AddPage(@cur_orientation);
2159 2160 @x = x;
2160 2161 if (ws > 0)
2161 2162 @ws = ws;
2162 2163 out(sprintf('%.3f Tw', ws * k));
2163 2164 end
2164 2165 else
2165 2166 @page += 1;
2166 2167 @y=@t_margin;
2167 2168 end
2168 2169 end
2169 2170
2170 2171 end
2171 2172 alias_method :ln, :Ln
2172 2173
2173 2174 #
2174 2175 # Returns the abscissa of the current position.
2175 2176 # @return float
2176 2177 # @since 1.2
2177 2178 # @see SetX(), GetY(), SetY()
2178 2179 #
2179 2180 def GetX()
2180 2181 #Get x position
2181 2182 return @x;
2182 2183 end
2183 2184 alias_method :get_x, :GetX
2184 2185
2185 2186 #
2186 2187 # Defines the abscissa of the current position. If the passed value is negative, it is relative to the right of the page.
2187 2188 # @param float :x The value of the abscissa.
2188 2189 # @since 1.2
2189 2190 # @see GetX(), GetY(), SetY(), SetXY()
2190 2191 #
2191 2192 def SetX(x)
2192 2193 #Set x position
2193 2194 if (x>=0)
2194 2195 @x = x;
2195 2196 else
2196 2197 @x=@w+x;
2197 2198 end
2198 2199 end
2199 2200 alias_method :set_x, :SetX
2200 2201
2201 2202 #
2202 2203 # Returns the ordinate of the current position.
2203 2204 # @return float
2204 2205 # @since 1.0
2205 2206 # @see SetY(), GetX(), SetX()
2206 2207 #
2207 2208 def GetY()
2208 2209 #Get y position
2209 2210 return @y;
2210 2211 end
2211 2212 alias_method :get_y, :GetY
2212 2213
2213 2214 #
2214 2215 # Moves the current abscissa back to the left margin and sets the ordinate. If the passed value is negative, it is relative to the bottom of the page.
2215 2216 # @param float :y The value of the ordinate.
2216 2217 # @since 1.0
2217 2218 # @see GetX(), GetY(), SetY(), SetXY()
2218 2219 #
2219 2220 def SetY(y)
2220 2221 #Set y position and reset x
2221 2222 @x=@l_margin;
2222 2223 if (y>=0)
2223 2224 @y = y;
2224 2225 else
2225 2226 @y=@h+y;
2226 2227 end
2227 2228 end
2228 2229 alias_method :set_y, :SetY
2229 2230
2230 2231 #
2231 2232 # Defines the abscissa and ordinate of the current position. If the passed values are negative, they are relative respectively to the right and bottom of the page.
2232 2233 # @param float :x The value of the abscissa
2233 2234 # @param float :y The value of the ordinate
2234 2235 # @since 1.2
2235 2236 # @see SetX(), SetY()
2236 2237 #
2237 2238 def SetXY(x, y)
2238 2239 #Set x and y positions
2239 2240 SetY(y);
2240 2241 SetX(x);
2241 2242 end
2242 2243 alias_method :set_xy, :SetXY
2243 2244
2244 2245 #
2245 2246 # Send the document to a given destination: string, local file or browser. In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
2246 2247 # The method first calls Close() if necessary to terminate the document.
2247 2248 # @param string :name The name of the file. If not given, the document will be sent to the browser (destination I) with the name doc.pdf.
2248 2249 # @param string :dest Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser. The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local file with the name given by name.</li><li>S: return the document as a string. name is ignored.</li></ul>If the parameter is not specified but a name is given, destination is F. If no parameter is specified at all, destination is I.<br />
2249 2250 # @since 1.0
2250 2251 # @see Close()
2251 2252 #
2252 2253 def Output(name='', dest='')
2253 2254 #Output PDF to some destination
2254 2255 #Finish document if necessary
2255 2256 if (@state < 3)
2256 2257 Close();
2257 2258 end
2258 2259 #Normalize parameters
2259 2260 # Boolean no longer supported
2260 2261 # if (dest.is_a?(Boolean))
2261 2262 # dest = dest ? 'D' : 'F';
2262 2263 # end
2263 2264 dest = dest.upcase
2264 2265 if (dest=='')
2265 2266 if (name=='')
2266 2267 name='doc.pdf';
2267 2268 dest='I';
2268 2269 else
2269 2270 dest='F';
2270 2271 end
2271 2272 end
2272 2273 case (dest)
2273 2274 when 'I'
2274 2275 # This is PHP specific code
2275 2276 ##Send to standard output
2276 2277 # if (ob_get_contents())
2277 2278 # Error('Some data has already been output, can\'t send PDF file');
2278 2279 # end
2279 2280 # if (php_sapi_name()!='cli')
2280 2281 # #We send to a browser
2281 2282 # header('Content-Type: application/pdf');
2282 2283 # if (headers_sent())
2283 2284 # Error('Some data has already been output to browser, can\'t send PDF file');
2284 2285 # end
2285 2286 # header('Content-Length: ' + @buffer.length);
2286 2287 # header('Content-disposition: inline; filename="' + name + '"');
2287 2288 # end
2288 2289 return @buffer;
2289 2290
2290 2291 when 'D'
2291 2292 # PHP specific
2292 2293 #Download file
2293 2294 # if (ob_get_contents())
2294 2295 # Error('Some data has already been output, can\'t send PDF file');
2295 2296 # end
2296 2297 # if (!_SERVER['HTTP_USER_AGENT'].nil? && SERVER['HTTP_USER_AGENT'].include?('MSIE'))
2297 2298 # header('Content-Type: application/force-download');
2298 2299 # else
2299 2300 # header('Content-Type: application/octet-stream');
2300 2301 # end
2301 2302 # if (headers_sent())
2302 2303 # Error('Some data has already been output to browser, can\'t send PDF file');
2303 2304 # end
2304 2305 # header('Content-Length: '+ @buffer.length);
2305 2306 # header('Content-disposition: attachment; filename="' + name + '"');
2306 2307 return @buffer;
2307 2308
2308 2309 when 'F'
2309 2310 open(name,'wb') do |f|
2310 2311 f.write(@buffer)
2311 2312 end
2312 2313 # PHP code
2313 2314 # #Save to local file
2314 2315 # f=open(name,'wb');
2315 2316 # if (!f)
2316 2317 # Error('Unable to create output file: ' + name);
2317 2318 # end
2318 2319 # fwrite(f,@buffer,@buffer.length);
2319 2320 # f.close
2320 2321
2321 2322 when 'S'
2322 2323 #Return as a string
2323 2324 return @buffer;
2324 2325 else
2325 2326 Error('Incorrect output destination: ' + dest);
2326 2327
2327 2328 end
2328 2329 return '';
2329 2330 end
2330 2331 alias_method :output, :Output
2331 2332
2332 2333 # Protected methods
2333 2334
2334 2335 #
2335 2336 # Check for locale-related bug
2336 2337 # @access protected
2337 2338 #
2338 2339 def dochecks()
2339 2340 #Check for locale-related bug
2340 2341 if (1.1==1)
2341 2342 Error('Don\'t alter the locale before including class file');
2342 2343 end
2343 2344 #Check for decimal separator
2344 2345 if (sprintf('%.1f',1.0)!='1.0')
2345 2346 setlocale(LC_NUMERIC,'C');
2346 2347 end
2347 2348 end
2348 2349
2349 2350 #
2350 2351 # Return fonts path
2351 2352 # @access protected
2352 2353 #
2353 2354 def getfontpath(file)
2354 2355 # Is it in the @@font_path?
2355 2356 if @@font_path
2356 2357 fpath = File.join @@font_path, file
2357 2358 if File.exists?(fpath)
2358 2359 return fpath
2359 2360 end
2360 2361 end
2361 2362 # Is it in this plugin's font folder?
2362 2363 fpath = File.join File.dirname(__FILE__), 'fonts', file
2363 2364 if File.exists?(fpath)
2364 2365 return fpath
2365 2366 end
2366 2367 # Could not find it.
2367 2368 nil
2368 2369 end
2369 2370
2370 2371 #
2371 2372 # Start document
2372 2373 # @access protected
2373 2374 #
2374 2375 def begindoc()
2375 2376 #Start document
2376 2377 @state=1;
2377 2378 out('%PDF-1.3');
2378 2379 end
2379 2380
2380 2381 #
2381 2382 # putpages
2382 2383 # @access protected
2383 2384 #
2384 2385 def putpages()
2385 2386 nb = @page;
2386 2387 if (@alias_nb_pages)
2387 2388 nbstr = UTF8ToUTF16BE(nb.to_s, false);
2388 2389 #Replace number of pages
2389 2390 1.upto(nb) do |n|
2390 2391 @pages[n].gsub!(@alias_nb_pages, nbstr)
2391 2392 end
2392 2393 end
2393 2394 if @def_orientation=='P'
2394 2395 w_pt=@fw_pt
2395 2396 h_pt=@fh_pt
2396 2397 else
2397 2398 w_pt=@fh_pt
2398 2399 h_pt=@fw_pt
2399 2400 end
2400 2401 filter=(@compress) ? '/Filter /FlateDecode ' : ''
2401 2402 1.upto(nb) do |n|
2402 2403 #Page
2403 2404 newobj
2404 2405 out('<</Type /Page')
2405 2406 out('/Parent 1 0 R')
2406 2407 unless @orientation_changes[n].nil?
2407 2408 out(sprintf('/MediaBox [0 0 %.2f %.2f]', h_pt, w_pt))
2408 2409 end
2409 2410 out('/Resources 2 0 R')
2410 2411 if @page_links[n]
2411 2412 #Links
2412 2413 annots='/Annots ['
2413 2414 @page_links[n].each do |pl|
2414 2415 rect=sprintf('%.2f %.2f %.2f %.2f', pl[0], pl[1], pl[0]+pl[2], pl[1]-pl[3]);
2415 2416 annots<<'<</Type /Annot /Subtype /Link /Rect [' + rect + '] /Border [0 0 0] ';
2416 2417 if (pl[4].is_a?(String))
2417 2418 annots<<'/A <</S /URI /URI (' + escape(pl[4]) + ')>>>>';
2418 2419 else
2419 2420 l=@links[pl[4]];
2420 2421 h=!@orientation_changes[l[0]].nil? ? w_pt : h_pt;
2421 2422 annots<<sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]>>',1+2*l[0], h-l[1]*@k);
2422 2423 end
2423 2424 end
2424 2425 out(annots + ']');
2425 2426 end
2426 2427 out('/Contents ' + (@n+1).to_s + ' 0 R>>');
2427 2428 out('endobj');
2428 2429 #Page content
2429 2430 p=(@compress) ? gzcompress(@pages[n]) : @pages[n];
2430 2431 newobj();
2431 2432 out('<<' + filter + '/Length '+ p.length.to_s + '>>');
2432 2433 putstream(p);
2433 2434 out('endobj');
2434 2435 end
2435 2436 #Pages root
2436 2437 @offsets[1]=@buffer.length;
2437 2438 out('1 0 obj');
2438 2439 out('<</Type /Pages');
2439 2440 kids='/Kids [';
2440 2441 0.upto(nb) do |i|
2441 2442 kids<<(3+2*i).to_s + ' 0 R ';
2442 2443 end
2443 2444 out(kids + ']');
2444 2445 out('/Count ' + nb.to_s);
2445 2446 out(sprintf('/MediaBox [0 0 %.2f %.2f]', w_pt, h_pt));
2446 2447 out('>>');
2447 2448 out('endobj');
2448 2449 end
2449 2450
2450 2451 #
2451 2452 # Adds fonts
2452 2453 # putfonts
2453 2454 # @access protected
2454 2455 #
2455 2456 def putfonts()
2456 2457 nf=@n;
2457 2458 @diffs.each do |diff|
2458 2459 #Encodings
2459 2460 newobj();
2460 2461 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [' + diff + ']>>');
2461 2462 out('endobj');
2462 2463 end
2463 2464 @font_files.each do |file, info|
2464 2465 #Font file embedding
2465 2466 newobj();
2466 2467 @font_files[file]['n']=@n;
2467 2468 font='';
2468 2469 open(getfontpath(file),'rb') do |f|
2469 2470 font = f.read();
2470 2471 end
2471 2472 compressed=(file[-2,2]=='.z');
2472 2473 if (!compressed && !info['length2'].nil?)
2473 2474 header=((font[0][0])==128);
2474 2475 if (header)
2475 2476 #Strip first binary header
2476 2477 font=font[6];
2477 2478 end
2478 2479 if header && (font[info['length1']][0] == 128)
2479 2480 #Strip second binary header
2480 2481 font=font[0..info['length1']] + font[info['length1']+6];
2481 2482 end
2482 2483 end
2483 2484 out('<</Length '+ font.length.to_s);
2484 2485 if (compressed)
2485 2486 out('/Filter /FlateDecode');
2486 2487 end
2487 2488 out('/Length1 ' + info['length1'].to_s);
2488 2489 if (!info['length2'].nil?)
2489 2490 out('/Length2 ' + info['length2'].to_s + ' /Length3 0');
2490 2491 end
2491 2492 out('>>');
2492 2493 open(getfontpath(file),'rb') do |f|
2493 2494 putstream(font)
2494 2495 end
2495 2496 out('endobj');
2496 2497 end
2497 2498 @fonts.each do |k, font|
2498 2499 #Font objects
2499 2500 @fonts[k]['n']=@n+1;
2500 2501 type = font['type'];
2501 2502 name = font['name'];
2502 2503 if (type=='core')
2503 2504 #Standard font
2504 2505 newobj();
2505 2506 out('<</Type /Font');
2506 2507 out('/BaseFont /' + name);
2507 2508 out('/Subtype /Type1');
2508 2509 if (name!='Symbol' && name!='ZapfDingbats')
2509 2510 out('/Encoding /WinAnsiEncoding');
2510 2511 end
2511 2512 out('>>');
2512 2513 out('endobj');
2513 2514 elsif type == 'Type0'
2514 2515 putType0(font)
2515 2516 elsif (type=='Type1' || type=='TrueType')
2516 2517 #Additional Type1 or TrueType font
2517 2518 newobj();
2518 2519 out('<</Type /Font');
2519 2520 out('/BaseFont /' + name);
2520 2521 out('/Subtype /' + type);
2521 2522 out('/FirstChar 32 /LastChar 255');
2522 2523 out('/Widths ' + (@n+1).to_s + ' 0 R');
2523 2524 out('/FontDescriptor ' + (@n+2).to_s + ' 0 R');
2524 2525 if (font['enc'])
2525 2526 if (!font['diff'].nil?)
2526 2527 out('/Encoding ' + (nf+font['diff']).to_s + ' 0 R');
2527 2528 else
2528 2529 out('/Encoding /WinAnsiEncoding');
2529 2530 end
2530 2531 end
2531 2532 out('>>');
2532 2533 out('endobj');
2533 2534 #Widths
2534 2535 newobj();
2535 2536 cw=font['cw']; # &
2536 2537 s='[';
2537 2538 32.upto(255) do |i|
2538 2539 s << cw[i.chr] + ' ';
2539 2540 end
2540 2541 out(s + ']');
2541 2542 out('endobj');
2542 2543 #Descriptor
2543 2544 newobj();
2544 2545 s='<</Type /FontDescriptor /FontName /' + name;
2545 2546 font['desc'].each do |k, v|
2546 2547 s<<' /' + k + ' ' + v;
2547 2548 end
2548 2549 file = font['file'];
2549 2550 if (file)
2550 2551 s<<' /FontFile' + (type=='Type1' ? '' : '2') + ' ' + @font_files[file]['n'] + ' 0 R';
2551 2552 end
2552 2553 out(s + '>>');
2553 2554 out('endobj');
2554 2555 else
2555 2556 #Allow for additional types
2556 2557 mtd='put' + type.downcase;
2557 2558 if (!self.respond_to?(mtd))
2558 2559 Error('Unsupported font type: ' + type)
2559 2560 else
2560 2561 self.send(mtd,font)
2561 2562 end
2562 2563 end
2563 2564 end
2564 2565 end
2565 2566
2566 2567 def putType0(font)
2567 2568 #Type0
2568 2569 newobj();
2569 2570 out('<</Type /Font')
2570 2571 out('/Subtype /Type0')
2571 2572 out('/BaseFont /'+font['name']+'-'+font['cMap'])
2572 2573 out('/Encoding /'+font['cMap'])
2573 2574 out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
2574 2575 out('>>')
2575 2576 out('endobj')
2576 2577 #CIDFont
2577 2578 newobj()
2578 2579 out('<</Type /Font')
2579 2580 out('/Subtype /CIDFontType0')
2580 2581 out('/BaseFont /'+font['name'])
2581 2582 out('/CIDSystemInfo <</Registry (Adobe) /Ordering ('+font['registry']['ordering']+') /Supplement '+font['registry']['supplement'].to_s+'>>')
2582 2583 out('/FontDescriptor '+(@n+1).to_s+' 0 R')
2583 2584 w='/W [1 ['
2584 2585 font['cw'].keys.sort.each {|key|
2585 2586 w+=font['cw'][key].to_s + " "
2586 2587 # ActionController::Base::logger.debug key.to_s
2587 2588 # ActionController::Base::logger.debug font['cw'][key].to_s
2588 2589 }
2589 2590 out(w+'] 231 325 500 631 [500] 326 389 500]')
2590 2591 out('>>')
2591 2592 out('endobj')
2592 2593 #Font descriptor
2593 2594 newobj()
2594 2595 out('<</Type /FontDescriptor')
2595 2596 out('/FontName /'+font['name'])
2596 2597 out('/Flags 6')
2597 2598 out('/FontBBox [0 -200 1000 900]')
2598 2599 out('/ItalicAngle 0')
2599 2600 out('/Ascent 800')
2600 2601 out('/Descent -200')
2601 2602 out('/CapHeight 800')
2602 2603 out('/StemV 60')
2603 2604 out('>>')
2604 2605 out('endobj')
2605 2606 end
2606 2607
2607 2608 #
2608 2609 # putimages
2609 2610 # @access protected
2610 2611 #
2611 2612 def putimages()
2612 2613 filter=(@compress) ? '/Filter /FlateDecode ' : '';
2613 2614 @images.each do |file, info| # was while(list(file, info)=each(@images))
2614 2615 newobj();
2615 2616 @images[file]['n']=@n;
2616 2617 out('<</Type /XObject');
2617 2618 out('/Subtype /Image');
2618 2619 out('/Width ' + info['w'].to_s);
2619 2620 out('/Height ' + info['h'].to_s);
2620 2621 if (info['cs']=='Indexed')
2621 2622 out('/ColorSpace [/Indexed /DeviceRGB ' + (info['pal'].length/3-1).to_s + ' ' + (@n+1).to_s + ' 0 R]');
2622 2623 else
2623 2624 out('/ColorSpace /' + info['cs']);
2624 2625 if (info['cs']=='DeviceCMYK')
2625 2626 out('/Decode [1 0 1 0 1 0 1 0]');
2626 2627 end
2627 2628 end
2628 2629 out('/BitsPerComponent ' + info['bpc'].to_s);
2629 2630 if (!info['f'].nil?)
2630 2631 out('/Filter /' + info['f']);
2631 2632 end
2632 2633 if (!info['parms'].nil?)
2633 2634 out(info['parms']);
2634 2635 end
2635 2636 if (!info['trns'].nil? and info['trns'].kind_of?(Array))
2636 2637 trns='';
2637 2638 0.upto(info['trns'].length) do |i|
2638 2639 trns << info['trns'][i] + ' ' + info['trns'][i] + ' ';
2639 2640 end
2640 2641 out('/Mask [' + trns + ']');
2641 2642 end
2642 2643 out('/Length ' + info['data'].length.to_s + '>>');
2643 2644 putstream(info['data']);
2644 2645 @images[file]['data']=nil
2645 2646 out('endobj');
2646 2647 #Palette
2647 2648 if (info['cs']=='Indexed')
2648 2649 newobj();
2649 2650 pal=(@compress) ? gzcompress(info['pal']) : info['pal'];
2650 2651 out('<<' + filter + '/Length ' + pal.length.to_s + '>>');
2651 2652 putstream(pal);
2652 2653 out('endobj');
2653 2654 end
2654 2655 end
2655 2656 end
2656 2657
2657 2658 #
2658 2659 # putxobjectdict
2659 2660 # @access protected
2660 2661 #
2661 2662 def putxobjectdict()
2662 2663 @images.each_value do |image|
2663 2664 out('/I' + image['i'].to_s + ' ' + image['n'].to_s + ' 0 R');
2664 2665 end
2665 2666 end
2666 2667
2667 2668 #
2668 2669 # putresourcedict
2669 2670 # @access protected
2670 2671 #
2671 2672 def putresourcedict()
2672 2673 out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
2673 2674 out('/Font <<');
2674 2675 @fonts.each_value do |font|
2675 2676 out('/F' + font['i'].to_s + ' ' + font['n'].to_s + ' 0 R');
2676 2677 end
2677 2678 out('>>');
2678 2679 out('/XObject <<');
2679 2680 putxobjectdict();
2680 2681 out('>>');
2681 2682 end
2682 2683
2683 2684 #
2684 2685 # putresources
2685 2686 # @access protected
2686 2687 #
2687 2688 def putresources()
2688 2689 putfonts();
2689 2690 putimages();
2690 2691 #Resource dictionary
2691 2692 @offsets[2]=@buffer.length;
2692 2693 out('2 0 obj');
2693 2694 out('<<');
2694 2695 putresourcedict();
2695 2696 out('>>');
2696 2697 out('endobj');
2697 2698 end
2698 2699
2699 2700 #
2700 2701 # putinfo
2701 2702 # @access protected
2702 2703 #
2703 2704 def putinfo()
2704 2705 out('/Producer ' + textstring(PDF_PRODUCER));
2705 2706 if (!@title.nil?)
2706 2707 out('/Title ' + textstring(@title));
2707 2708 end
2708 2709 if (!@subject.nil?)
2709 2710 out('/Subject ' + textstring(@subject));
2710 2711 end
2711 2712 if (!@author.nil?)
2712 2713 out('/Author ' + textstring(@author));
2713 2714 end
2714 2715 if (!@keywords.nil?)
2715 2716 out('/Keywords ' + textstring(@keywords));
2716 2717 end
2717 2718 if (!@creator.nil?)
2718 2719 out('/Creator ' + textstring(@creator));
2719 2720 end
2720 2721 out('/CreationDate ' + textstring('D:' + Time.now.strftime('%Y%m%d%H%M%S')));
2721 2722 end
2722 2723
2723 2724 #
2724 2725 # putcatalog
2725 2726 # @access protected
2726 2727 #
2727 2728 def putcatalog()
2728 2729 out('/Type /Catalog');
2729 2730 out('/Pages 1 0 R');
2730 2731 if (@zoom_mode=='fullpage')
2731 2732 out('/OpenAction [3 0 R /Fit]');
2732 2733 elsif (@zoom_mode=='fullwidth')
2733 2734 out('/OpenAction [3 0 R /FitH null]');
2734 2735 elsif (@zoom_mode=='real')
2735 2736 out('/OpenAction [3 0 R /XYZ null null 1]');
2736 2737 elsif (!@zoom_mode.is_a?(String))
2737 2738 out('/OpenAction [3 0 R /XYZ null null ' + (@zoom_mode/100) + ']');
2738 2739 end
2739 2740 if (@layout_mode=='single')
2740 2741 out('/PageLayout /SinglePage');
2741 2742 elsif (@layout_mode=='continuous')
2742 2743 out('/PageLayout /OneColumn');
2743 2744 elsif (@layout_mode=='two')
2744 2745 out('/PageLayout /TwoColumnLeft');
2745 2746 end
2746 2747 end
2747 2748
2748 2749 #
2749 2750 # puttrailer
2750 2751 # @access protected
2751 2752 #
2752 2753 def puttrailer()
2753 2754 out('/Size ' + (@n+1).to_s);
2754 2755 out('/Root ' + @n.to_s + ' 0 R');
2755 2756 out('/Info ' + (@n-1).to_s + ' 0 R');
2756 2757 end
2757 2758
2758 2759 #
2759 2760 # putheader
2760 2761 # @access protected
2761 2762 #
2762 2763 def putheader()
2763 2764 out('%PDF-' + @pdf_version);
2764 2765 end
2765 2766
2766 2767 #
2767 2768 # enddoc
2768 2769 # @access protected
2769 2770 #
2770 2771 def enddoc()
2771 2772 putheader();
2772 2773 putpages();
2773 2774 putresources();
2774 2775 #Info
2775 2776 newobj();
2776 2777 out('<<');
2777 2778 putinfo();
2778 2779 out('>>');
2779 2780 out('endobj');
2780 2781 #Catalog
2781 2782 newobj();
2782 2783 out('<<');
2783 2784 putcatalog();
2784 2785 out('>>');
2785 2786 out('endobj');
2786 2787 #Cross-ref
2787 2788 o=@buffer.length;
2788 2789 out('xref');
2789 2790 out('0 ' + (@n+1).to_s);
2790 2791 out('0000000000 65535 f ');
2791 2792 1.upto(@n) do |i|
2792 2793 out(sprintf('%010d 00000 n ',@offsets[i]));
2793 2794 end
2794 2795 #Trailer
2795 2796 out('trailer');
2796 2797 out('<<');
2797 2798 puttrailer();
2798 2799 out('>>');
2799 2800 out('startxref');
2800 2801 out(o);
2801 2802 out('%%EOF');
2802 2803 @state=3;
2803 2804 end
2804 2805
2805 2806 #
2806 2807 # beginpage
2807 2808 # @access protected
2808 2809 #
2809 2810 def beginpage(orientation)
2810 2811 @page += 1;
2811 2812 @pages[@page]='';
2812 2813 @state=2;
2813 2814 @x=@l_margin;
2814 2815 @y=@t_margin;
2815 2816 @font_family='';
2816 2817 #Page orientation
2817 2818 if (orientation.empty?)
2818 2819 orientation=@def_orientation;
2819 2820 else
2820 2821 orientation.upcase!
2821 2822 if (orientation!=@def_orientation)
2822 2823 @orientation_changes[@page]=true;
2823 2824 end
2824 2825 end
2825 2826 if (orientation!=@cur_orientation)
2826 2827 #Change orientation
2827 2828 if (orientation=='P')
2828 2829 @w_pt=@fw_pt;
2829 2830 @h_pt=@fh_pt;
2830 2831 @w=@fw;
2831 2832 @h=@fh;
2832 2833 else
2833 2834 @w_pt=@fh_pt;
2834 2835 @h_pt=@fw_pt;
2835 2836 @w=@fh;
2836 2837 @h=@fw;
2837 2838 end
2838 2839 @page_break_trigger=@h-@b_margin;
2839 2840 @cur_orientation = orientation;
2840 2841 end
2841 2842 end
2842 2843
2843 2844 #
2844 2845 # End of page contents
2845 2846 # @access protected
2846 2847 #
2847 2848 def endpage()
2848 2849 @state=1;
2849 2850 end
2850 2851
2851 2852 #
2852 2853 # Begin a new object
2853 2854 # @access protected
2854 2855 #
2855 2856 def newobj()
2856 2857 @n += 1;
2857 2858 @offsets[@n]=@buffer.length;
2858 2859 out(@n.to_s + ' 0 obj');
2859 2860 end
2860 2861
2861 2862 #
2862 2863 # Underline and Deleted text
2863 2864 # @access protected
2864 2865 #
2865 2866 def dolinetxt(x, y, txt)
2866 2867 up = @current_font['up'];
2867 2868 ut = @current_font['ut'];
2868 2869 w = GetStringWidth(txt) + @ws * txt.count(' ');
2869 2870 sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - (y - up / 1000.0 * @font_size)) * @k, w * @k, -ut / 1000.0 * @font_size_pt);
2870 2871 end
2871 2872
2872 2873 #
2873 2874 # Extract info from a JPEG file
2874 2875 # @access protected
2875 2876 #
2876 2877 def parsejpg(file)
2877 2878 a=getimagesize(file);
2878 2879 if (a.empty?)
2879 2880 Error('Missing or incorrect image file: ' + file);
2880 2881 end
2881 2882 if (!a[2].nil? and a[2]!='JPEG')
2882 2883 Error('Not a JPEG file: ' + file);
2883 2884 end
2884 2885 if (a['channels'].nil? or a['channels']==3)
2885 2886 colspace='DeviceRGB';
2886 2887 elsif (a['channels']==4)
2887 2888 colspace='DeviceCMYK';
2888 2889 else
2889 2890 colspace='DeviceGray';
2890 2891 end
2891 2892 bpc=!a['bits'].nil? ? a['bits'] : 8;
2892 2893 #Read whole file
2893 2894 data='';
2894 2895
2895 2896 open( @@k_path_cache + File::basename(file),'rb') do |f|
2896 2897 data<<f.read();
2897 2898 end
2898 2899 File.delete( @@k_path_cache + File::basename(file))
2899 2900
2900 2901 return {'w' => a[0],'h' => a[1],'cs' => colspace,'bpc' => bpc,'f'=>'DCTDecode','data' => data}
2901 2902 end
2902 2903
2903 2904 #
2904 2905 # Extract info from a PNG file
2905 2906 # @access protected
2906 2907 #
2907 2908 def parsepng(file)
2908 2909 f=open(file,'rb');
2909 2910 #Check signature
2910 2911 if (f.read(8)!=137.chr + 'PNG' + 13.chr + 10.chr + 26.chr + 10.chr)
2911 2912 Error('Not a PNG file: ' + file);
2912 2913 end
2913 2914 #Read header chunk
2914 2915 f.read(4);
2915 2916 if (f.read(4)!='IHDR')
2916 2917 Error('Incorrect PNG file: ' + file);
2917 2918 end
2918 2919 w=freadint(f);
2919 2920 h=freadint(f);
2920 2921 bpc=f.read(1).unpack('C')[0];
2921 2922 if (bpc>8)
2922 2923 Error('16-bit depth not supported: ' + file);
2923 2924 end
2924 2925 ct=f.read(1).unpack('C')[0];
2925 2926 if (ct==0)
2926 2927 colspace='DeviceGray';
2927 2928 elsif (ct==2)
2928 2929 colspace='DeviceRGB';
2929 2930 elsif (ct==3)
2930 2931 colspace='Indexed';
2931 2932 else
2932 2933 Error('Alpha channel not supported: ' + file);
2933 2934 end
2934 2935 if (f.read(1).unpack('C')[0] != 0)
2935 2936 Error('Unknown compression method: ' + file);
2936 2937 end
2937 2938 if (f.read(1).unpack('C')[0] != 0)
2938 2939 Error('Unknown filter method: ' + file);
2939 2940 end
2940 2941 if (f.read(1).unpack('C')[0] != 0)
2941 2942 Error('Interlacing not supported: ' + file);
2942 2943 end
2943 2944 f.read(4);
2944 2945 parms='/DecodeParms <</Predictor 15 /Colors ' + (ct==2 ? 3 : 1).to_s + ' /BitsPerComponent ' + bpc.to_s + ' /Columns ' + w.to_s + '>>';
2945 2946 #Scan chunks looking for palette, transparency and image data
2946 2947 pal='';
2947 2948 trns='';
2948 2949 data='';
2949 2950 begin
2950 2951 n=freadint(f);
2951 2952 type=f.read(4);
2952 2953 if (type=='PLTE')
2953 2954 #Read palette
2954 2955 pal=f.read( n);
2955 2956 f.read(4);
2956 2957 elsif (type=='tRNS')
2957 2958 #Read transparency info
2958 2959 t=f.read( n);
2959 2960 if (ct==0)
2960 2961 trns = t[1].unpack('C')[0]
2961 2962 elsif (ct==2)
2962 2963 trns = t[[1].unpack('C')[0], t[3].unpack('C')[0], t[5].unpack('C')[0]]
2963 2964 else
2964 2965 pos=t.include?(0.chr);
2965 2966 if (pos!=false)
2966 2967 trns = [pos]
2967 2968 end
2968 2969 end
2969 2970 f.read(4);
2970 2971 elsif (type=='IDAT')
2971 2972 #Read image data block
2972 2973 data<<f.read( n);
2973 2974 f.read(4);
2974 2975 elsif (type=='IEND')
2975 2976 break;
2976 2977 else
2977 2978 f.read( n+4);
2978 2979 end
2979 2980 end while(n)
2980 2981 if (colspace=='Indexed' and pal.empty?)
2981 2982 Error('Missing palette in ' + file);
2982 2983 end
2983 2984 f.close
2984 2985 return {'w' => w, 'h' => h, 'cs' => colspace, 'bpc' => bpc, 'f'=>'FlateDecode', 'parms' => parms, 'pal' => pal, 'trns' => trns, 'data' => data}
2985 2986 end
2986 2987
2987 2988 #
2988 2989 # Read a 4-byte integer from file
2989 2990 # @access protected
2990 2991 #
2991 2992 def freadint(f)
2992 2993 # Read a 4-byte integer from file
2993 2994 a = f.read(4).unpack('N')
2994 2995 return a[0]
2995 2996 end
2996 2997
2997 2998 #
2998 2999 # Format a text string
2999 3000 # @access protected
3000 3001 #
3001 3002 def textstring(s)
3002 3003 if (@is_unicode)
3003 3004 #Convert string to UTF-16BE
3004 3005 s = UTF8ToUTF16BE(s, true);
3005 3006 end
3006 3007 return '(' + escape(s) + ')';
3007 3008 end
3008 3009
3009 3010 #
3010 3011 # Format a text string
3011 3012 # @access protected
3012 3013 #
3013 3014 def escapetext(s)
3014 3015 if (@is_unicode)
3015 3016 #Convert string to UTF-16BE
3016 3017 s = UTF8ToUTF16BE(s, false);
3017 3018 end
3018 3019 return escape(s);
3019 3020 end
3020 3021
3021 3022 #
3022 3023 # Add \ before \, ( and )
3023 3024 # @access protected
3024 3025 #
3025 3026 def escape(s)
3026 3027 # Add \ before \, ( and )
3027 3028 s.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)').gsub(13.chr, '\r')
3028 3029 end
3029 3030
3030 3031 #
3031 3032 #
3032 3033 # @access protected
3033 3034 #
3034 3035 def putstream(s)
3035 3036 out('stream');
3036 3037 out(s);
3037 3038 out('endstream');
3038 3039 end
3039 3040
3040 3041 #
3041 3042 # Add a line to the document
3042 3043 # @access protected
3043 3044 #
3044 3045 def out(s)
3045 3046 if (@state==2)
3046 3047 @pages[@page] << s.to_s + "\n";
3047 3048 else
3048 3049 @buffer << s.to_s + "\n";
3049 3050 end
3050 3051 end
3051 3052
3052 3053 #
3053 3054 # Adds unicode fonts.<br>
3054 3055 # Based on PDF Reference 1.3 (section 5)
3055 3056 # @access protected
3056 3057 # @author Nicola Asuni
3057 3058 # @since 1.52.0.TC005 (2005-01-05)
3058 3059 #
3059 3060 def puttruetypeunicode(font)
3060 3061 # Type0 Font
3061 3062 # A composite font composed of other fonts, organized hierarchically
3062 3063 newobj();
3063 3064 out('<</Type /Font');
3064 3065 out('/Subtype /Type0');
3065 3066 out('/BaseFont /' + font['name'] + '');
3066 3067 out('/Encoding /Identity-H'); #The horizontal identity mapping for 2-byte CIDs; may be used with CIDFonts using any Registry, Ordering, and Supplement values.
3067 3068 out('/DescendantFonts [' + (@n + 1).to_s + ' 0 R]');
3068 3069 out('/ToUnicode ' + (@n + 2).to_s + ' 0 R');
3069 3070 out('>>');
3070 3071 out('endobj');
3071 3072
3072 3073 # CIDFontType2
3073 3074 # A CIDFont whose glyph descriptions are based on TrueType font technology
3074 3075 newobj();
3075 3076 out('<</Type /Font');
3076 3077 out('/Subtype /CIDFontType2');
3077 3078 out('/BaseFont /' + font['name'] + '');
3078 3079 out('/CIDSystemInfo ' + (@n + 2).to_s + ' 0 R');
3079 3080 out('/FontDescriptor ' + (@n + 3).to_s + ' 0 R');
3080 3081 if (!font['desc']['MissingWidth'].nil?)
3081 3082 out('/DW ' + font['desc']['MissingWidth'].to_s + ''); # The default width for glyphs in the CIDFont MissingWidth
3082 3083 end
3083 3084 w = "";
3084 3085 font['cw'].each do |cid, width|
3085 3086 w << '' + cid.to_s + ' [' + width.to_s + '] '; # define a specific width for each individual CID
3086 3087 end
3087 3088 out('/W [' + w + ']'); # A description of the widths for the glyphs in the CIDFont
3088 3089 out('/CIDToGIDMap ' + (@n + 4).to_s + ' 0 R');
3089 3090 out('>>');
3090 3091 out('endobj');
3091 3092
3092 3093 # ToUnicode
3093 3094 # is a stream object that contains the definition of the CMap
3094 3095 # (PDF Reference 1.3 chap. 5.9)
3095 3096 newobj();
3096 3097 out('<</Length 383>>');
3097 3098 out('stream');
3098 3099 out('/CIDInit /ProcSet findresource begin');
3099 3100 out('12 dict begin');
3100 3101 out('begincmap');
3101 3102 out('/CIDSystemInfo');
3102 3103 out('<</Registry (Adobe)');
3103 3104 out('/Ordering (UCS)');
3104 3105 out('/Supplement 0');
3105 3106 out('>> def');
3106 3107 out('/CMapName /Adobe-Identity-UCS def');
3107 3108 out('/CMapType 2 def');
3108 3109 out('1 begincodespacerange');
3109 3110 out('<0000> <FFFF>');
3110 3111 out('endcodespacerange');
3111 3112 out('1 beginbfrange');
3112 3113 out('<0000> <FFFF> <0000>');
3113 3114 out('endbfrange');
3114 3115 out('endcmap');
3115 3116 out('CMapName currentdict /CMap defineresource pop');
3116 3117 out('end');
3117 3118 out('end');
3118 3119 out('endstream');
3119 3120 out('endobj');
3120 3121
3121 3122 # CIDSystemInfo dictionary
3122 3123 # A dictionary containing entries that define the character collection of the CIDFont.
3123 3124 newobj();
3124 3125 out('<</Registry (Adobe)'); # A string identifying an issuer of character collections
3125 3126 out('/Ordering (UCS)'); # A string that uniquely names a character collection issued by a specific registry
3126 3127 out('/Supplement 0'); # The supplement number of the character collection.
3127 3128 out('>>');
3128 3129 out('endobj');
3129 3130
3130 3131 # Font descriptor
3131 3132 # A font descriptor describing the CIDFont default metrics other than its glyph widths
3132 3133 newobj();
3133 3134 out('<</Type /FontDescriptor');
3134 3135 out('/FontName /' + font['name']);
3135 3136 font['desc'].each do |key, value|
3136 3137 out('/' + key.to_s + ' ' + value.to_s);
3137 3138 end
3138 3139 if (font['file'])
3139 3140 # A stream containing a TrueType font program
3140 3141 out('/FontFile2 ' + @font_files[font['file']]['n'].to_s + ' 0 R');
3141 3142 end
3142 3143 out('>>');
3143 3144 out('endobj');
3144 3145
3145 3146 # Embed CIDToGIDMap
3146 3147 # A specification of the mapping from CIDs to glyph indices
3147 3148 newobj();
3148 3149 ctgfile = getfontpath(font['ctg'])
3149 3150 if (!ctgfile)
3150 3151 Error('Font file not found: ' + ctgfile);
3151 3152 end
3152 3153 size = File.size(ctgfile);
3153 3154 out('<</Length ' + size.to_s + '');
3154 3155 if (ctgfile[-2,2] == '.z') # check file extension
3155 3156 # Decompresses data encoded using the public-domain
3156 3157 # zlib/deflate compression method, reproducing the
3157 3158 # original text or binary data#
3158 3159 out('/Filter /FlateDecode');
3159 3160 end
3160 3161 out('>>');
3161 3162 open(ctgfile, "rb") do |f|
3162 3163 putstream(f.read())
3163 3164 end
3164 3165 out('endobj');
3165 3166 end
3166 3167
3167 3168 #
3168 3169 # Converts UTF-8 strings to codepoints array.<br>
3169 3170 # Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
3170 3171 # Based on: http://www.faqs.org/rfcs/rfc3629.html
3171 3172 # <pre>
3172 3173 # Char. number range | UTF-8 octet sequence
3173 3174 # (hexadecimal) | (binary)
3174 3175 # --------------------+-----------------------------------------------
3175 3176 # 0000 0000-0000 007F | 0xxxxxxx
3176 3177 # 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
3177 3178 # 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
3178 3179 # 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
3179 3180 # ---------------------------------------------------------------------
3180 3181 #
3181 3182 # ABFN notation:
3182 3183 # ---------------------------------------------------------------------
3183 3184 # UTF8-octets =#( UTF8-char )
3184 3185 # UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
3185 3186 # UTF8-1 = %x00-7F
3186 3187 # UTF8-2 = %xC2-DF UTF8-tail
3187 3188 #
3188 3189 # UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
3189 3190 # %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
3190 3191 # UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
3191 3192 # %xF4 %x80-8F 2( UTF8-tail )
3192 3193 # UTF8-tail = %x80-BF
3193 3194 # ---------------------------------------------------------------------
3194 3195 # </pre>
3195 3196 # @param string :str string to process.
3196 3197 # @return array containing codepoints (UTF-8 characters values)
3197 3198 # @access protected
3198 3199 # @author Nicola Asuni
3199 3200 # @since 1.53.0.TC005 (2005-01-05)
3200 3201 #
3201 3202 def UTF8StringToArray(str)
3202 3203 if (!@is_unicode)
3203 3204 return str; # string is not in unicode
3204 3205 end
3205 3206
3206 3207 unicode = [] # array containing unicode values
3207 3208 bytes = [] # array containing single character byte sequences
3208 3209 numbytes = 1; # number of octetc needed to represent the UTF-8 character
3209 3210
3210 3211 str = str.to_s; # force :str to be a string
3211 3212
3212 3213 str.each_byte do |char|
3213 3214 if (bytes.length == 0) # get starting octect
3214 3215 if (char <= 0x7F)
3215 3216 unicode << char # use the character "as is" because is ASCII
3216 3217 numbytes = 1
3217 3218 elsif ((char >> 0x05) == 0x06) # 2 bytes character (0x06 = 110 BIN)
3218 3219 bytes << ((char - 0xC0) << 0x06)
3219 3220 numbytes = 2
3220 3221 elsif ((char >> 0x04) == 0x0E) # 3 bytes character (0x0E = 1110 BIN)
3221 3222 bytes << ((char - 0xE0) << 0x0C)
3222 3223 numbytes = 3
3223 3224 elsif ((char >> 0x03) == 0x1E) # 4 bytes character (0x1E = 11110 BIN)
3224 3225 bytes << ((char - 0xF0) << 0x12)
3225 3226 numbytes = 4
3226 3227 else
3227 3228 # use replacement character for other invalid sequences
3228 3229 unicode << 0xFFFD
3229 3230 bytes = []
3230 3231 numbytes = 1
3231 3232 end
3232 3233 elsif ((char >> 0x06) == 0x02) # bytes 2, 3 and 4 must start with 0x02 = 10 BIN
3233 3234 bytes << (char - 0x80)
3234 3235 if (bytes.length == numbytes)
3235 3236 # compose UTF-8 bytes to a single unicode value
3236 3237 char = bytes[0]
3237 3238 1.upto(numbytes-1) do |j|
3238 3239 char += (bytes[j] << ((numbytes - j - 1) * 0x06))
3239 3240 end
3240 3241 if (((char >= 0xD800) and (char <= 0xDFFF)) or (char >= 0x10FFFF))
3241 3242 # The definition of UTF-8 prohibits encoding character numbers between
3242 3243 # U+D800 and U+DFFF, which are reserved for use with the UTF-16
3243 3244 # encoding form (as surrogate pairs) and do not directly represent
3244 3245 # characters
3245 3246 unicode << 0xFFFD; # use replacement character
3246 3247 else
3247 3248 unicode << char; # add char to array
3248 3249 end
3249 3250 # reset data for next char
3250 3251 bytes = []
3251 3252 numbytes = 1;
3252 3253 end
3253 3254 else
3254 3255 # use replacement character for other invalid sequences
3255 3256 unicode << 0xFFFD;
3256 3257 bytes = []
3257 3258 numbytes = 1;
3258 3259 end
3259 3260 end
3260 3261 return unicode;
3261 3262 end
3262 3263
3263 3264 #
3264 3265 # Converts UTF-8 strings to UTF16-BE.<br>
3265 3266 # Based on: http://www.faqs.org/rfcs/rfc2781.html
3266 3267 # <pre>
3267 3268 # Encoding UTF-16:
3268 3269 #
3269 3270 # Encoding of a single character from an ISO 10646 character value to
3270 3271 # UTF-16 proceeds as follows. Let U be the character number, no greater
3271 3272 # than 0x10FFFF.
3272 3273 #
3273 3274 # 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
3274 3275 # terminate.
3275 3276 #
3276 3277 # 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
3277 3278 # U' must be less than or equal to 0xFFFFF. That is, U' can be
3278 3279 # represented in 20 bits.
3279 3280 #
3280 3281 # 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
3281 3282 # 0xDC00, respectively. These integers each have 10 bits free to
3282 3283 # encode the character value, for a total of 20 bits.
3283 3284 #
3284 3285 # 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
3285 3286 # bits of W1 and the 10 low-order bits of U' to the 10 low-order
3286 3287 # bits of W2. Terminate.
3287 3288 #
3288 3289 # Graphically, steps 2 through 4 look like:
3289 3290 # U' = yyyyyyyyyyxxxxxxxxxx
3290 3291 # W1 = 110110yyyyyyyyyy
3291 3292 # W2 = 110111xxxxxxxxxx
3292 3293 # </pre>
3293 3294 # @param string :str string to process.
3294 3295 # @param boolean :setbom if true set the Byte Order Mark (BOM = 0xFEFF)
3295 3296 # @return string
3296 3297 # @access protected
3297 3298 # @author Nicola Asuni
3298 3299 # @since 1.53.0.TC005 (2005-01-05)
3299 3300 # @uses UTF8StringToArray
3300 3301 #
3301 3302 def UTF8ToUTF16BE(str, setbom=true)
3302 3303 if (!@is_unicode)
3303 3304 return str; # string is not in unicode
3304 3305 end
3305 3306 outstr = ""; # string to be returned
3306 3307 unicode = UTF8StringToArray(str); # array containing UTF-8 unicode values
3307 3308 numitems = unicode.length;
3308 3309
3309 3310 if (setbom)
3310 3311 outstr << "\xFE\xFF"; # Byte Order Mark (BOM)
3311 3312 end
3312 3313 unicode.each do |char|
3313 3314 if (char == 0xFFFD)
3314 3315 outstr << "\xFF\xFD"; # replacement character
3315 3316 elsif (char < 0x10000)
3316 3317 outstr << (char >> 0x08).chr;
3317 3318 outstr << (char & 0xFF).chr;
3318 3319 else
3319 3320 char -= 0x10000;
3320 3321 w1 = 0xD800 | (char >> 0x10);
3321 3322 w2 = 0xDC00 | (char & 0x3FF);
3322 3323 outstr << (w1 >> 0x08).chr;
3323 3324 outstr << (w1 & 0xFF).chr;
3324 3325 outstr << (w2 >> 0x08).chr;
3325 3326 outstr << (w2 & 0xFF).chr;
3326 3327 end
3327 3328 end
3328 3329 return outstr;
3329 3330 end
3330 3331
3331 3332 # ====================================================
3332 3333
3333 3334 #
3334 3335 # Set header font.
3335 3336 # @param array :font font
3336 3337 # @since 1.1
3337 3338 #
3338 3339 def SetHeaderFont(font)
3339 3340 @header_font = font;
3340 3341 end
3341 3342 alias_method :set_header_font, :SetHeaderFont
3342 3343
3343 3344 #
3344 3345 # Set footer font.
3345 3346 # @param array :font font
3346 3347 # @since 1.1
3347 3348 #
3348 3349 def SetFooterFont(font)
3349 3350 @footer_font = font;
3350 3351 end
3351 3352 alias_method :set_footer_font, :SetFooterFont
3352 3353
3353 3354 #
3354 3355 # Set language array.
3355 3356 # @param array :language
3356 3357 # @since 1.1
3357 3358 #
3358 3359 def SetLanguageArray(language)
3359 3360 @l = language;
3360 3361 end
3361 3362 alias_method :set_language_array, :SetLanguageArray
3362 3363 #
3363 3364 # Set document barcode.
3364 3365 # @param string :bc barcode
3365 3366 #
3366 3367 def SetBarcode(bc="")
3367 3368 @barcode = bc;
3368 3369 end
3369 3370
3370 3371 #
3371 3372 # Print Barcode.
3372 3373 # @param int :x x position in user units
3373 3374 # @param int :y y position in user units
3374 3375 # @param int :w width in user units
3375 3376 # @param int :h height position in user units
3376 3377 # @param string :type type of barcode (I25, C128A, C128B, C128C, C39)
3377 3378 # @param string :style barcode style
3378 3379 # @param string :font font for text
3379 3380 # @param int :xres x resolution
3380 3381 # @param string :code code to print
3381 3382 #
3382 3383 def writeBarcode(x, y, w, h, type, style, font, xres, code)
3383 3384 require(File.dirname(__FILE__) + "/barcode/barcode.rb");
3384 3385 require(File.dirname(__FILE__) + "/barcode/i25object.rb");
3385 3386 require(File.dirname(__FILE__) + "/barcode/c39object.rb");
3386 3387 require(File.dirname(__FILE__) + "/barcode/c128aobject.rb");
3387 3388 require(File.dirname(__FILE__) + "/barcode/c128bobject.rb");
3388 3389 require(File.dirname(__FILE__) + "/barcode/c128cobject.rb");
3389 3390
3390 3391 if (code.empty?)
3391 3392 return;
3392 3393 end
3393 3394
3394 3395 if (style.empty?)
3395 3396 style = BCS_ALIGN_LEFT;
3396 3397 style |= BCS_IMAGE_PNG;
3397 3398 style |= BCS_TRANSPARENT;
3398 3399 #:style |= BCS_BORDER;
3399 3400 #:style |= BCS_DRAW_TEXT;
3400 3401 #:style |= BCS_STRETCH_TEXT;
3401 3402 #:style |= BCS_REVERSE_COLOR;
3402 3403 end
3403 3404 if (font.empty?) then font = BCD_DEFAULT_FONT; end
3404 3405 if (xres.empty?) then xres = BCD_DEFAULT_XRES; end
3405 3406
3406 3407 scale_factor = 1.5 * xres * @k;
3407 3408 bc_w = (w * scale_factor).round #width in points
3408 3409 bc_h = (h * scale_factor).round #height in points
3409 3410
3410 3411 case (type.upcase)
3411 3412 when "I25"
3412 3413 obj = I25Object.new(bc_w, bc_h, style, code);
3413 3414 when "C128A"
3414 3415 obj = C128AObject.new(bc_w, bc_h, style, code);
3415 3416 when "C128B"
3416 3417 obj = C128BObject.new(bc_w, bc_h, style, code);
3417 3418 when "C128C"
3418 3419 obj = C128CObject.new(bc_w, bc_h, style, code);
3419 3420 when "C39"
3420 3421 obj = C39Object.new(bc_w, bc_h, style, code);
3421 3422 end
3422 3423
3423 3424 obj.SetFont(font);
3424 3425 obj.DrawObject(xres);
3425 3426
3426 3427 #use a temporary file....
3427 3428 tmpName = tempnam(@@k_path_cache,'img');
3428 3429 imagepng(obj.getImage(), tmpName);
3429 3430 Image(tmpName, x, y, w, h, 'png');
3430 3431 obj.DestroyObject();
3431 3432 obj = nil
3432 3433 unlink(tmpName);
3433 3434 end
3434 3435
3435 3436 #
3436 3437 # Returns the PDF data.
3437 3438 #
3438 3439 def GetPDFData()
3439 3440 if (@state < 3)
3440 3441 Close();
3441 3442 end
3442 3443 return @buffer;
3443 3444 end
3444 3445
3445 3446 # --- HTML PARSER FUNCTIONS ---
3446 3447
3447 3448 #
3448 3449 # Allows to preserve some HTML formatting.<br />
3449 3450 # Supports: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, ins, del, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small
3450 3451 # @param string :html text to display
3451 3452 # @param boolean :ln if true add a new line after text (default = true)
3452 3453 # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0.
3453 3454 #
3454 3455 def writeHTML(html, ln=true, fill=0, h=0)
3455 3456
3456 3457 @lasth = h if h > 0
3457 3458 if (@lasth == 0)
3458 3459 #set row height
3459 3460 @lasth = @font_size * @@k_cell_height_ratio;
3460 3461 end
3461 3462
3462 3463 @href = nil
3463 3464 @style = "";
3464 3465 @t_cells = [[]];
3465 3466 @table_id = 0;
3466 3467
3467 3468 # pre calculate
3468 3469 html.split(/(<[^>]+>)/).each do |element|
3469 3470 if "<" == element[0,1]
3470 3471 #Tag
3471 3472 if (element[1, 1] == '/')
3472 3473 closedHTMLTagCalc(element[2..-2].downcase);
3473 3474 else
3474 3475 #Extract attributes
3475 3476 # get tag name
3476 3477 tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0}
3477 3478 tag = tag[0].to_s.downcase;
3478 3479
3479 3480 # get attributes
3480 3481 attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)
3481 3482 attrs = {}
3482 3483 attr_array.each do |name, value|
3483 3484 attrs[name.downcase] = value;
3484 3485 end
3485 3486 openHTMLTagCalc(tag, attrs);
3486 3487 end
3487 3488 end
3488 3489 end
3489 3490 @table_id = 0;
3490 3491
3491 3492 html.split(/(<[A-Za-z!?\/][^>]*?>)/).each do |element|
3492 3493 if "<" == element[0,1]
3493 3494 #Tag
3494 3495 if (element[1, 1] == '/')
3495 3496 closedHTMLTagHandler(element[2..-2].downcase);
3496 3497 else
3497 3498 #Extract attributes
3498 3499 # get tag name
3499 3500 tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0}
3500 3501 tag = tag[0].to_s.downcase;
3501 3502
3502 3503 # get attributes
3503 3504 attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)
3504 3505 attrs = {}
3505 3506 attr_array.each do |name, value|
3506 3507 attrs[name.downcase] = value;
3507 3508 end
3508 3509 openHTMLTagHandler(tag, attrs, fill);
3509 3510 end
3510 3511
3511 3512 else
3512 3513 #Text
3513 if (@href)
3514 if (@tdbegin)
3514 3515 element.gsub!(/[\t\r\n\f]/, "");
3515 addHtmlLink(@href, element, fill);
3516 elsif (@tdbegin)
3516 @tdtext << element.gsub(/&nbsp;/, " ");
3517 elsif (@href)
3517 3518 element.gsub!(/[\t\r\n\f]/, "");
3518 element.gsub!(/&nbsp;/, " ");
3519 base_page = @page;
3520 base_x = @x;
3521 base_y = @y;
3522
3523 MultiCell(@tdwidth, @tdheight, unhtmlentities(element.strip), @tableborder, @tdalign, @tdfill, 1);
3524 tr_end = @t_cells[@table_id][@tr_id][@td_id]['j1'] + 1;
3525 if @max_td_page[tr_end].nil? or (@max_td_page[tr_end] < @page)
3526 @max_td_page[tr_end] = @page
3527 @max_td_y[tr_end] = @y
3528 elsif (@max_td_page[tr_end] == @page)
3529 @max_td_y[tr_end] = @y if @max_td_y[tr_end].nil? or (@max_td_y[tr_end] < @y)
3530 end
3531
3532 @page = base_page;
3533 @x = base_x + @tdwidth;
3534 @y = base_y;
3519 addHtmlLink(@href, element, fill);
3535 3520 elsif (@pre_state == true and element.length > 0)
3536 3521 Write(@lasth, unhtmlentities(element), '', fill);
3537 3522 elsif (element.strip.length > 0)
3538 3523 element.gsub!(/[\t\r\n\f]/, "");
3539 3524 element.gsub!(/&nbsp;/, " ");
3540 3525 Write(@lasth, unhtmlentities(element), '', fill);
3541 3526 end
3542 3527 end
3543 3528 end
3544 3529
3545 3530 if (ln)
3546 3531 Ln(@lasth);
3547 3532 end
3548 3533 end
3549 3534 alias_method :write_html, :writeHTML
3550 3535
3551 3536 #
3552 3537 # Prints a cell (rectangular area) with optional borders, background color and html text string. The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.<br />
3553 3538 # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
3554 3539 # @param float :w Cell width. If 0, the cell extends up to the right margin.
3555 3540 # @param float :h Cell minimum height. The cell extends automatically if needed.
3556 3541 # @param float :x upper-left corner X coordinate
3557 3542 # @param float :y upper-left corner Y coordinate
3558 3543 # @param string :html html text to print. Default value: empty string.
3559 3544 # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
3560 3545 # @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
3561 3546 # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
3562 3547 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
3563 3548 # @see Cell()
3564 3549 #
3565 3550 def writeHTMLCell(w, h, x, y, html='', border=0, ln=1, fill=0)
3566 3551
3567 3552 if (@lasth == 0)
3568 3553 #set row height
3569 3554 @lasth = @font_size * @@k_cell_height_ratio;
3570 3555 end
3571 3556
3572 3557 if (x == 0)
3573 3558 x = GetX();
3574 3559 end
3575 3560 if (y == 0)
3576 3561 y = GetY();
3577 3562 end
3578 3563
3579 3564 # get current page number
3580 3565 pagenum = @page;
3581 3566
3582 3567 SetX(x);
3583 3568 SetY(y);
3584 3569
3585 3570 if (w == 0)
3586 3571 w = @fw - x - @r_margin;
3587 3572 end
3588 3573
3589 3574 b=0;
3590 3575 if (border)
3591 3576 if (border==1)
3592 3577 border='LTRB';
3593 3578 b='LRT';
3594 3579 b2='LR';
3595 3580 elsif border.is_a?(String)
3596 3581 b2='';
3597 3582 if (border.include?('L'))
3598 3583 b2<<'L';
3599 3584 end
3600 3585 if (border.include?('R'))
3601 3586 b2<<'R';
3602 3587 end
3603 3588 b=(border.include?('T')) ? b2 + 'T' : b2;
3604 3589 end
3605 3590 end
3606 3591
3607 3592 # store original margin values
3608 3593 l_margin = @l_margin;
3609 3594 r_margin = @r_margin;
3610 3595
3611 3596 # set new margin values
3612 3597 SetLeftMargin(x);
3613 3598 SetRightMargin(@fw - x - w);
3614 3599
3615 3600 # calculate remaining vertical space on page
3616 3601 restspace = GetPageHeight() - GetY() - GetBreakMargin();
3617 3602
3618 3603 writeHTML(html, true, fill); # write html text
3619 3604
3620 3605 currentY = GetY();
3621 3606
3622 3607 @auto_page_break = false;
3623 3608 # check if a new page has been created
3624 3609 if (@page > pagenum)
3625 3610 # design a cell around the text on first page
3626 3611 currentpage = @page;
3627 3612 @page = pagenum;
3628 3613 SetY(GetPageHeight() - restspace - GetBreakMargin());
3629 3614 Cell(w, restspace - 1, "", b, 0, 'L', 0);
3630 3615 b = b2;
3631 3616 @page += 1;
3632 3617 while @page < currentpage
3633 3618 SetY(@t_margin); # put cursor at the beginning of text
3634 3619 Cell(w, @page_break_trigger - @t_margin, "", b, 0, 'L', 0);
3635 3620 @page += 1;
3636 3621 end
3637 3622 if (border.is_a?(String) and border.include?('B'))
3638 3623 b<<'B';
3639 3624 end
3640 3625 # design a cell around the text on last page
3641 3626 SetY(@t_margin); # put cursor at the beginning of text
3642 3627 Cell(w, currentY - @t_margin, "", b, 0, 'L', 0);
3643 3628 else
3644 3629 SetY(y); # put cursor at the beginning of text
3645 3630 # design a cell around the text
3646 3631 Cell(w, [h, (currentY - y)].max, "", border, 0, 'L', 0);
3647 3632 end
3648 3633 @auto_page_break = true;
3649 3634
3650 3635 # restore original margin values
3651 3636 SetLeftMargin(l_margin);
3652 3637 SetRightMargin(r_margin);
3653 3638
3654 3639 @lasth = h
3655 3640
3656 3641 # move cursor to specified position
3657 3642 if (ln == 0)
3658 3643 # go to the top-right of the cell
3659 3644 @x = x + w;
3660 3645 @y = y;
3661 3646 elsif (ln == 1)
3662 3647 # go to the beginning of the next line
3663 3648 @x = @l_margin;
3664 3649 @y = currentY;
3665 3650 elsif (ln == 2)
3666 3651 # go to the bottom-left of the cell (below)
3667 3652 @x = x;
3668 3653 @y = currentY;
3669 3654 end
3670 3655 end
3671 3656 alias_method :write_html_cell, :writeHTMLCell
3672 3657
3673 3658 #
3674 3659 # Check html table tag position.
3675 3660 #
3676 3661 # @param array :table potision array
3677 3662 # @param int :current tr tag id number
3678 3663 # @param int :current td tag id number
3679 3664 # @access private
3680 3665 # @return int : next td_id position.
3681 3666 # value 0 mean that can use position.
3682 3667 #
3683 3668 def checkTableBlockingCellPosition(table, tr_id, td_id )
3684 3669 0.upto(tr_id) do |j|
3685 3670 0.upto(@t_cells[table][j].size - 1) do |i|
3686 3671 if @t_cells[table][j][i]['i0'] <= td_id and td_id <= @t_cells[table][j][i]['i1']
3687 3672 if @t_cells[table][j][i]['j0'] <= tr_id and tr_id <= @t_cells[table][j][i]['j1']
3688 3673 return @t_cells[table][j][i]['i1'] - td_id + 1;
3689 3674 end
3690 3675 end
3691 3676 end
3692 3677 end
3693 3678 return 0;
3694 3679 end
3695 3680
3696 3681 #
3697 3682 # Calculate opening tags.
3698 3683 #
3699 3684 # html table cell array : @t_cells
3700 3685 #
3701 3686 # i0: table cell start position
3702 3687 # i1: table cell end position
3703 3688 # j0: table row start position
3704 3689 # j1: table row end position
3705 3690 #
3706 3691 # +------+
3707 3692 # |i0,j0 |
3708 3693 # | i1,j1|
3709 3694 # +------+
3710 3695 #
3711 3696 # example html:
3712 3697 # <table>
3713 3698 # <tr><td></td><td></td><td></td></tr>
3714 3699 # <tr><td colspan=2></td><td></td></tr>
3715 3700 # <tr><td rowspan=2></td><td></td><td></td></tr>
3716 3701 # <tr><td></td><td></td></tr>
3717 3702 # </table>
3718 3703 #
3719 3704 # i: 0 1 2
3720 3705 # j+----+----+----+
3721 3706 # :|0,0 |1,0 |2,0 |
3722 3707 # 0| 0,0| 1,0| 2,0|
3723 3708 # +----+----+----+
3724 3709 # |0,1 |2,1 |
3725 3710 # 1| 1,1| 2,1|
3726 3711 # +----+----+----+
3727 3712 # |0,2 |1,2 |2,2 |
3728 3713 # 2| | 1,2| 2,2|
3729 3714 # + +----+----+
3730 3715 # | |1,3 |2,3 |
3731 3716 # 3| 0,3| 1,3| 2,3|
3732 3717 # +----+----+----+
3733 3718 #
3734 3719 # html table cell array :
3735 3720 # [[[i0=>0,j0=>0,i1=>0,j1=>0],[i0=>1,j0=>0,i1=>1,j1=>0],[i0=>2,j0=>0,i1=>2,j1=>0]],
3736 3721 # [[i0=>0,j0=>1,i1=>1,j1=>1],[i0=>2,j0=>1,i1=>2,j1=>1]],
3737 3722 # [[i0=>0,j0=>2,i1=>0,j1=>3],[i0=>1,j0=>2,i1=>1,j1=>2],[i0=>2,j0=>2,i1=>2,j1=>2]]
3738 3723 # [[i0=>1,j0=>3,i1=>1,j1=>3],[i0=>2,j0=>3,i1=>2,j1=>3]]]
3739 3724 #
3740 3725 # @param string :tag tag name (in upcase)
3741 3726 # @param string :attr tag attribute (in upcase)
3742 3727 # @access private
3743 3728 #
3744 3729 def openHTMLTagCalc(tag, attrs)
3745 3730 #Opening tag
3746 3731 case (tag)
3747 3732 when 'table'
3748 3733 @max_table_columns[@table_id] = 0;
3749 3734 @t_columns = 0;
3750 3735 @tr_id = -1;
3751 3736 when 'tr'
3752 3737 if @max_table_columns[@table_id] < @t_columns
3753 3738 @max_table_columns[@table_id] = @t_columns;
3754 3739 end
3755 3740 @t_columns = 0;
3756 3741 @tr_id += 1;
3757 3742 @td_id = -1;
3758 3743 @t_cells[@table_id].push []
3759 3744 when 'td', 'th'
3760 3745 @td_id += 1;
3761 3746 if attrs['colspan'].nil? or attrs['colspan'] == ''
3762 3747 colspan = 1;
3763 3748 else
3764 3749 colspan = attrs['colspan'].to_i;
3765 3750 end
3766 3751 if attrs['rowspan'].nil? or attrs['rowspan'] == ''
3767 3752 rowspan = 1;
3768 3753 else
3769 3754 rowspan = attrs['rowspan'].to_i;
3770 3755 end
3771 3756
3772 3757 i = 0;
3773 3758 while true
3774 3759 next_i_distance = checkTableBlockingCellPosition(@table_id, @tr_id, @td_id + i);
3775 3760 if next_i_distance == 0
3776 3761 @t_cells[@table_id][@tr_id].push "i0"=>@td_id + i, "j0"=>@tr_id, "i1"=>(@td_id + i + colspan - 1), "j1"=>@tr_id + rowspan - 1
3777 3762 break;
3778 3763 end
3779 3764 i += next_i_distance;
3780 3765 end
3781 3766
3782 3767 @t_columns += colspan;
3783 3768 end
3784 3769 end
3785 3770
3786 3771 #
3787 3772 # Calculate closing tags.
3788 3773 # @param string :tag tag name (in upcase)
3789 3774 # @access private
3790 3775 #
3791 3776 def closedHTMLTagCalc(tag)
3792 3777 #Closing tag
3793 3778 case (tag)
3794 3779 when 'table'
3795 3780 if @max_table_columns[@table_id] < @t_columns
3796 3781 @max_table_columns[@table_id] = @t_columns;
3797 3782 end
3798 3783 @table_id += 1;
3799 3784 @t_cells.push []
3800 3785 end
3801 3786 end
3802 3787
3803 3788 #
3804 3789 # Convert to accessible file path
3805 3790 # @param string :attrname image file name
3806 3791 #
3807 3792 def getImageFilename( attrname )
3808 3793 nil
3809 3794 end
3810 3795
3811 3796 #
3812 3797 # Process opening tags.
3813 3798 # @param string :tag tag name (in upcase)
3814 3799 # @param string :attr tag attribute (in upcase)
3815 3800 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
3816 3801 # @access private
3817 3802 #
3818 3803 def openHTMLTagHandler(tag, attrs, fill=0)
3819 3804 #Opening tag
3820 3805 case (tag)
3821 3806 when 'pre'
3822 3807 @pre_state = true;
3823 3808 @l_margin += 5;
3824 3809 @r_margin += 5;
3825 3810 @x += 5;
3826 3811
3827 3812 when 'table'
3813 Ln();
3828 3814 if @default_table_columns < @max_table_columns[@table_id]
3829 3815 @table_columns = @max_table_columns[@table_id];
3830 3816 else
3831 3817 @table_columns = @default_table_columns;
3832 3818 end
3833 3819 @l_margin += 5;
3834 3820 @r_margin += 5;
3835 3821 @x += 5;
3836 3822
3837 3823 if attrs['border'].nil? or attrs['border'] == ''
3838 3824 @tableborder = 0;
3839 3825 else
3840 3826 @tableborder = attrs['border'];
3841 3827 end
3842 3828 @tr_id = -1;
3843 3829 @max_td_page[0] = @page;
3844 3830 @max_td_y[0] = @y;
3845 3831
3846 3832 when 'tr', 'td', 'th'
3847 3833 if tag == 'th'
3848 3834 SetStyle('b', true);
3849 3835 @tdalign = "C";
3850 3836 end
3851 3837 if ((!attrs['width'].nil?) and (attrs['width'] != ''))
3852 3838 @tdwidth = (attrs['width'].to_i/4);
3853 3839 else
3854 3840 @tdwidth = ((@w - @l_margin - @r_margin) / @table_columns);
3855 3841 end
3856 3842
3857 3843 if tag == 'tr'
3858 3844 @tr_id += 1;
3859 3845 @td_id = -1;
3860 3846 else
3861 3847 @td_id += 1;
3862 3848 @x = @l_margin + @tdwidth * @t_cells[@table_id][@tr_id][@td_id]['i0'];
3863 3849 end
3864 3850
3865 3851 if attrs['colspan'].nil? or attrs['border'] == ''
3866 3852 @colspan = 1;
3867 3853 else
3868 3854 @colspan = attrs['colspan'].to_i;
3869 3855 end
3870 3856 @tdwidth *= @colspan;
3871 3857 if ((!attrs['height'].nil?) and (attrs['height'] != ''))
3872 3858 @tdheight=(attrs['height'].to_i / @k);
3873 3859 else
3874 3860 @tdheight = @lasth;
3875 3861 end
3876 3862 if ((!attrs['align'].nil?) and (attrs['align'] != ''))
3877 3863 case (attrs['align'])
3878 3864 when 'center'
3879 3865 @tdalign = "C";
3880 3866 when 'right'
3881 3867 @tdalign = "R";
3882 3868 when 'left'
3883 3869 @tdalign = "L";
3884 3870 end
3885 3871 end
3886 3872 if ((!attrs['bgcolor'].nil?) and (attrs['bgcolor'] != ''))
3887 3873 coul = convertColorHexToDec(attrs['bgcolor']);
3888 3874 SetFillColor(coul['R'], coul['G'], coul['B']);
3889 3875 @tdfill=1;
3890 3876 end
3891 3877 @tdbegin=true;
3892 3878
3893 3879 when 'hr'
3894 3880 margin = 1;
3895 3881 if ((!attrs['width'].nil?) and (attrs['width'] != ''))
3896 3882 hrWidth = attrs['width'];
3897 3883 else
3898 3884 hrWidth = @w - @l_margin - @r_margin - margin;
3899 3885 end
3900 3886 SetLineWidth(0.2);
3901 3887 Line(@x + margin, @y, @x + hrWidth, @y);
3902 3888 Ln();
3903 3889
3904 3890 when 'strong'
3905 3891 SetStyle('b', true);
3906 3892
3907 3893 when 'em'
3908 3894 SetStyle('i', true);
3909 3895
3910 3896 when 'ins'
3911 3897 SetStyle('u', true);
3912 3898
3913 3899 when 'del'
3914 3900 SetStyle('d', true);
3915 3901
3916 3902 when 'b', 'i', 'u'
3917 3903 SetStyle(tag, true);
3918 3904
3919 3905 when 'a'
3920 3906 @href = attrs['href'];
3921 3907
3922 3908 when 'img'
3923 3909 if (!attrs['src'].nil?)
3910 # Don't generates image inside table tag
3911 if (@tdbegin)
3912 @tdtext << attrs['src'];
3913 return
3914 end
3924 3915 # Only generates image include a pdf if RMagick is avalaible
3925 3916 unless Object.const_defined?(:Magick)
3926 3917 Write(@lasth, attrs['src'], '', fill);
3927 3918 return
3928 3919 end
3929 3920 file = getImageFilename(attrs['src'])
3930 3921 if (file.nil?)
3931 3922 Write(@lasth, attrs['src'], '', fill);
3932 3923 return
3933 3924 end
3934 3925
3935 3926 if (attrs['width'].nil?)
3936 3927 attrs['width'] = 0;
3937 3928 end
3938 3929 if (attrs['height'].nil?)
3939 3930 attrs['height'] = 0;
3940 3931 end
3941 3932
3942 3933 begin
3943 3934 Image(file, GetX(),GetY(), pixelsToMillimeters(attrs['width']), pixelsToMillimeters(attrs['height']));
3944 3935 #SetX(@img_rb_x);
3945 3936 SetY(@img_rb_y);
3946 3937 rescue => err
3947 3938 logger.error "pdf: Image: error: #{err.message}"
3948 3939 Write(@lasth, attrs['src'], '', fill);
3949 3940 if File.file?( @@k_path_cache + File::basename(file))
3950 3941 File.delete( @@k_path_cache + File::basename(file))
3951 3942 end
3952 3943 end
3953 3944 end
3954 3945
3955 3946 when 'ul', 'ol'
3956 3947 if @li_count == 0
3957 3948 Ln() if @prevquote_count == @quote_count; # insert Ln for keeping quote lines
3958 3949 @prevquote_count = @quote_count;
3959 3950 end
3960 3951 if @li_state == true
3961 3952 Ln();
3962 3953 @li_state = false;
3963 3954 end
3964 3955 if tag == 'ul'
3965 3956 @list_ordered[@li_count] = false;
3966 3957 else
3967 3958 @list_ordered[@li_count] = true;
3968 3959 end
3969 3960 @list_count[@li_count] = 0;
3970 3961 @li_count += 1
3971 3962
3972 3963 when 'li'
3973 3964 Ln() if @li_state == true
3974 3965 if (@list_ordered[@li_count - 1])
3975 3966 @list_count[@li_count - 1] += 1;
3976 3967 @li_spacer = " " * @li_count + (@list_count[@li_count - 1]).to_s + ". ";
3977 3968 else
3978 3969 #unordered list simbol
3979 3970 @li_spacer = " " * @li_count + "- ";
3980 3971 end
3981 3972 Write(@lasth, @spacer + @li_spacer, '', fill);
3982 3973 @li_state = true;
3983 3974
3984 3975 when 'blockquote'
3985 3976 if (@quote_count == 0)
3986 3977 SetStyle('i', true);
3987 3978 @l_margin += 5;
3988 3979 else
3989 3980 @l_margin += 5 / 2;
3990 3981 end
3991 3982 @x = @l_margin;
3992 3983 @quote_top[@quote_count] = @y;
3993 3984 @quote_page[@quote_count] = @page;
3994 3985 @quote_count += 1
3995 3986 when 'br'
3996 3987 Ln();
3997 3988
3998 3989 if (@li_spacer.length > 0)
3999 3990 @x += GetStringWidth(@li_spacer);
4000 3991 end
4001 3992
4002 3993 when 'p'
4003 3994 Ln();
4004 3995 0.upto(@quote_count - 1) do |i|
4005 3996 if @quote_page[i] == @page;
4006 3997 if @quote_top[i] == @y - @lasth; # fix start line
4007 3998 @quote_top[i] = @y;
4008 3999 end
4009 4000 else
4010 4001 if @quote_page[i] == @page - 1;
4011 4002 @quote_page[i] = @page; # fix start line
4012 4003 @quote_top[i] = @t_margin;
4013 4004 end
4014 4005 end
4015 4006 end
4016 4007
4017 4008 when 'sup'
4018 4009 currentfont_size = @font_size;
4019 4010 @tempfontsize = @font_size_pt;
4020 4011 SetFontSize(@font_size_pt * @@k_small_ratio);
4021 4012 SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio)));
4022 4013
4023 4014 when 'sub'
4024 4015 currentfont_size = @font_size;
4025 4016 @tempfontsize = @font_size_pt;
4026 4017 SetFontSize(@font_size_pt * @@k_small_ratio);
4027 4018 SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio)));
4028 4019
4029 4020 when 'small'
4030 4021 currentfont_size = @font_size;
4031 4022 @tempfontsize = @font_size_pt;
4032 4023 SetFontSize(@font_size_pt * @@k_small_ratio);
4033 4024 SetXY(GetX(), GetY() + ((currentfont_size - @font_size)/3));
4034 4025
4035 4026 when 'font'
4036 4027 if (!attrs['color'].nil? and attrs['color']!='')
4037 4028 coul = convertColorHexToDec(attrs['color']);
4038 4029 SetTextColor(coul['R'], coul['G'], coul['B']);
4039 4030 @issetcolor=true;
4040 4031 end
4041 4032 if (!attrs['face'].nil? and @fontlist.include?(attrs['face'].downcase))
4042 4033 SetFont(attrs['face'].downcase);
4043 4034 @issetfont=true;
4044 4035 end
4045 4036 if (!attrs['size'].nil?)
4046 4037 headsize = attrs['size'].to_i;
4047 4038 else
4048 4039 headsize = 0;
4049 4040 end
4050 4041 currentfont_size = @font_size;
4051 4042 @tempfontsize = @font_size_pt;
4052 4043 SetFontSize(@font_size_pt + headsize);
4053 4044 @lasth = @font_size * @@k_cell_height_ratio;
4054 4045
4055 4046 when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
4056 4047 Ln();
4057 4048 headsize = (4 - tag[1,1].to_f) * 2
4058 4049 @tempfontsize = @font_size_pt;
4059 4050 SetFontSize(@font_size_pt + headsize);
4060 4051 SetStyle('b', true);
4061 4052 @lasth = @font_size * @@k_cell_height_ratio;
4062 4053
4063 4054 end
4064 4055 end
4065 4056
4066 4057 #
4067 4058 # Process closing tags.
4068 4059 # @param string :tag tag name (in upcase)
4069 4060 # @access private
4070 4061 #
4071 4062 def closedHTMLTagHandler(tag)
4072 4063 #Closing tag
4073 4064 case (tag)
4074 4065 when 'pre'
4075 4066 @pre_state = false;
4076 4067 @l_margin -= 5;
4077 4068 @r_margin -= 5;
4078 4069 @x = @l_margin;
4079 4070 Ln();
4080 4071
4081 4072 when 'td','th'
4073 base_page = @page;
4074 base_x = @x;
4075 base_y = @y;
4076
4077 MultiCell(@tdwidth, @tdheight, unhtmlentities(@tdtext.strip), @tableborder, @tdalign, @tdfill, 1);
4078 tr_end = @t_cells[@table_id][@tr_id][@td_id]['j1'] + 1;
4079 if @max_td_page[tr_end].nil? or (@max_td_page[tr_end] < @page)
4080 @max_td_page[tr_end] = @page
4081 @max_td_y[tr_end] = @y
4082 elsif (@max_td_page[tr_end] == @page)
4083 @max_td_y[tr_end] = @y if @max_td_y[tr_end].nil? or (@max_td_y[tr_end] < @y)
4084 end
4085
4086 @page = base_page;
4087 @x = base_x + @tdwidth;
4088 @y = base_y;
4089 @tdtext = '';
4082 4090 @tdbegin = false;
4083 4091 @tdwidth = 0;
4084 4092 @tdheight = 0;
4085 4093 @tdalign = "L";
4086 4094 SetStyle('b', false);
4087 4095 @tdfill = 0;
4088 4096 SetFillColor(@prevfill_color[0], @prevfill_color[1], @prevfill_color[2]);
4089 4097
4090 4098 when 'tr'
4091 4099 @y = @max_td_y[@tr_id + 1];
4092 4100 @x = @l_margin;
4093 4101 @page = @max_td_page[@tr_id + 1];
4094 4102
4095 4103 when 'table'
4096 4104 # Write Table Line
4097 4105 width = (@w - @l_margin - @r_margin) / @table_columns;
4098 4106 0.upto(@t_cells[@table_id].size - 1) do |j|
4099 4107 0.upto(@t_cells[@table_id][j].size - 1) do |i|
4100 4108 @page = @max_td_page[j]
4101 4109 i0=@t_cells[@table_id][j][i]['i0'];
4102 4110 j0=@t_cells[@table_id][j][i]['j0'];
4103 4111 i1=@t_cells[@table_id][j][i]['i1'];
4104 4112 j1=@t_cells[@table_id][j][i]['j1'];
4105 4113
4106 4114 Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * (i1+1), @max_td_y[j0]) # top
4107 4115 if ( @page == @max_td_page[j1 + 1])
4108 4116 Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * i0, @max_td_y[j1+1]) # left
4109 4117 Line(@l_margin + width * (i1+1), @max_td_y[j0], @l_margin + width * (i1+1), @max_td_y[j1+1]) # right
4110 4118 else
4111 4119 Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * i0, @page_break_trigger) # left
4112 4120 Line(@l_margin + width * (i1+1), @max_td_y[j0], @l_margin + width * (i1+1), @page_break_trigger) # right
4113 4121 @page += 1;
4114 4122 while @page < @max_td_page[j1 + 1]
4115 4123 Line(@l_margin + width * i0, @t_margin, @l_margin + width * i0, @page_break_trigger) # left
4116 4124 Line(@l_margin + width * (i1+1), @t_margin, @l_margin + width * (i1+1), @page_break_trigger) # right
4117 4125 @page += 1;
4118 4126 end
4119 4127 Line(@l_margin + width * i0, @t_margin, @l_margin + width * i0, @max_td_y[j1+1]) # left
4120 4128 Line(@l_margin + width * (i1+1), @t_margin, @l_margin + width * (i1+1), @max_td_y[j1+1]) # right
4121 4129 end
4122 4130 Line(@l_margin + width * i0, @max_td_y[j1+1], @l_margin + width * (i1+1), @max_td_y[j1+1]) # bottom
4123 4131 end
4124 4132 end
4125 4133
4126 4134 @l_margin -= 5;
4127 4135 @r_margin -= 5;
4128 4136 @tableborder=0;
4129 Ln();
4130 4137 @table_id += 1;
4131 4138
4132 4139 when 'strong'
4133 4140 SetStyle('b', false);
4134 4141
4135 4142 when 'em'
4136 4143 SetStyle('i', false);
4137 4144
4138 4145 when 'ins'
4139 4146 SetStyle('u', false);
4140 4147
4141 4148 when 'del'
4142 4149 SetStyle('d', false);
4143 4150
4144 4151 when 'b', 'i', 'u'
4145 4152 SetStyle(tag, false);
4146 4153
4147 4154 when 'a'
4148 4155 @href = nil;
4149 4156
4150 4157 when 'p'
4151 4158 Ln();
4152 4159
4153 4160 when 'sup'
4154 4161 currentfont_size = @font_size;
4155 4162 SetFontSize(@tempfontsize);
4156 4163 @tempfontsize = @font_size_pt;
4157 4164 SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio)));
4158 4165
4159 4166 when 'sub'
4160 4167 currentfont_size = @font_size;
4161 4168 SetFontSize(@tempfontsize);
4162 4169 @tempfontsize = @font_size_pt;
4163 4170 SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio)));
4164 4171
4165 4172 when 'small'
4166 4173 currentfont_size = @font_size;
4167 4174 SetFontSize(@tempfontsize);
4168 4175 @tempfontsize = @font_size_pt;
4169 4176 SetXY(GetX(), GetY() - ((@font_size - currentfont_size)/3));
4170 4177
4171 4178 when 'font'
4172 4179 if (@issetcolor == true)
4173 4180 SetTextColor(@prevtext_color[0], @prevtext_color[1], @prevtext_color[2]);
4174 4181 end
4175 4182 if (@issetfont)
4176 4183 @font_family = @prevfont_family;
4177 4184 @font_style = @prevfont_style;
4178 4185 SetFont(@font_family);
4179 4186 @issetfont = false;
4180 4187 end
4181 4188 currentfont_size = @font_size;
4182 4189 SetFontSize(@tempfontsize);
4183 4190 @tempfontsize = @font_size_pt;
4184 4191 #@text_color = @prevtext_color;
4185 4192 @lasth = @font_size * @@k_cell_height_ratio;
4186 4193
4187 4194 when 'blockquote'
4188 4195 @quote_count -= 1
4189 4196 if (@quote_page[@quote_count] == @page)
4190 4197 Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @y) # quoto line
4191 4198 else
4192 4199 cur_page = @page;
4193 4200 cur_y = @y;
4194 4201 @page = @quote_page[@quote_count];
4195 4202 if (@quote_top[@quote_count] < @page_break_trigger)
4196 4203 Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @page_break_trigger) # quoto line
4197 4204 end
4198 4205 @page += 1;
4199 4206 while @page < cur_page
4200 4207 Line(@l_margin - 1, @t_margin, @l_margin - 1, @page_break_trigger) # quoto line
4201 4208 @page += 1;
4202 4209 end
4203 4210 @y = cur_y;
4204 4211 Line(@l_margin - 1, @t_margin, @l_margin - 1, @y) # quoto line
4205 4212 end
4206 4213 if (@quote_count <= 0)
4207 4214 SetStyle('i', false);
4208 4215 @l_margin -= 5;
4209 4216 else
4210 4217 @l_margin -= 5 / 2;
4211 4218 end
4212 4219 @x = @l_margin;
4213 4220 Ln() if @quote_count == 0
4214 4221
4215 4222 when 'ul', 'ol'
4216 4223 @li_count -= 1
4217 4224 if @li_state == true
4218 4225 Ln();
4219 4226 @li_state = false;
4220 4227 end
4221 4228
4222 4229 when 'li'
4223 4230 @li_spacer = "";
4224 4231 if @li_state == true
4225 4232 Ln();
4226 4233 @li_state = false;
4227 4234 end
4228 4235
4229 4236 when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
4230 4237 SetFontSize(@tempfontsize);
4231 4238 @tempfontsize = @font_size_pt;
4232 4239 SetStyle('b', false);
4233 4240 Ln();
4234 4241 @lasth = @font_size * @@k_cell_height_ratio;
4235 4242
4236 4243 if tag == 'h1' or tag == 'h2' or tag == 'h3' or tag == 'h4'
4237 4244 margin = 1;
4238 4245 hrWidth = @w - @l_margin - @r_margin - margin;
4239 4246 if tag == 'h1' or tag == 'h2'
4240 4247 SetLineWidth(0.2);
4241 4248 else
4242 4249 SetLineWidth(0.1);
4243 4250 end
4244 4251 Line(@x + margin, @y, @x + hrWidth, @y);
4245 4252 end
4246 4253 end
4247 4254 end
4248 4255
4249 4256 #
4250 4257 # Sets font style.
4251 4258 # @param string :tag tag name (in lowercase)
4252 4259 # @param boolean :enable
4253 4260 # @access private
4254 4261 #
4255 4262 def SetStyle(tag, enable)
4256 4263 #Modify style and select corresponding font
4257 4264 ['b', 'i', 'u', 'd'].each do |s|
4258 4265 if tag.downcase == s
4259 4266 if enable
4260 4267 @style << s if ! @style.include?(s)
4261 4268 else
4262 4269 @style = @style.gsub(s,'')
4263 4270 end
4264 4271 end
4265 4272 end
4266 4273 SetFont('', @style);
4267 4274 end
4268 4275
4269 4276 #
4270 4277 # Output anchor link.
4271 4278 # @param string :url link URL
4272 4279 # @param string :name link name
4273 4280 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
4274 4281 # @access public
4275 4282 #
4276 4283 def addHtmlLink(url, name, fill=0)
4277 4284 #Put a hyperlink
4278 4285 SetTextColor(0, 0, 255);
4279 4286 SetStyle('u', true);
4280 4287 Write(@lasth, name, url, fill);
4281 4288 SetStyle('u', false);
4282 4289 SetTextColor(0);
4283 4290 end
4284 4291
4285 4292 #
4286 4293 # Returns an associative array (keys: R,G,B) from
4287 4294 # a hex html code (e.g. #3FE5AA).
4288 4295 # @param string :color hexadecimal html color [#rrggbb]
4289 4296 # @return array
4290 4297 # @access private
4291 4298 #
4292 4299 def convertColorHexToDec(color = "#000000")
4293 4300 tbl_color = {}
4294 4301 tbl_color['R'] = color[1,2].hex.to_i;
4295 4302 tbl_color['G'] = color[3,2].hex.to_i;
4296 4303 tbl_color['B'] = color[5,2].hex.to_i;
4297 4304 return tbl_color;
4298 4305 end
4299 4306
4300 4307 #
4301 4308 # Converts pixels to millimeters in 72 dpi.
4302 4309 # @param int :px pixels
4303 4310 # @return float millimeters
4304 4311 # @access private
4305 4312 #
4306 4313 def pixelsToMillimeters(px)
4307 4314 return px.to_f * 25.4 / 72;
4308 4315 end
4309 4316
4310 4317 #
4311 4318 # Reverse function for htmlentities.
4312 4319 # Convert entities in UTF-8.
4313 4320 #
4314 4321 # @param :text_to_convert Text to convert.
4315 4322 # @return string converted
4316 4323 #
4317 4324 def unhtmlentities(string)
4318 4325 if @@decoder.nil?
4319 4326 CGI.unescapeHTML(string)
4320 4327 else
4321 4328 @@decoder.decode(string)
4322 4329 end
4323 4330 end
4324 4331
4325 4332 end # END OF CLASS
4326 4333
4327 4334 #TODO 2007-05-25 (EJM) Level=0 -
4328 4335 #Handle special IE contype request
4329 4336 # if (!_SERVER['HTTP_USER_AGENT'].nil? and (_SERVER['HTTP_USER_AGENT']=='contype'))
4330 4337 # header('Content-Type: application/pdf');
4331 4338 # exit;
4332 4339 # }
General Comments 0
You need to be logged in to leave comments. Login now