Project

General

Profile

Feature #30492 » 30492-replace-with-minimagick.patch

Yuichi HARADA, 2019-02-07 05:24

View differences:

Gemfile
32 32

  
33 33
platforms :mri, :mingw, :x64_mingw do
34 34
  # Optional gem for exporting the gantt to a PNG file, not supported with jruby
35
  group :rmagick do
36
    gem "rmagick", ">= 2.14.0"
35
  group :minimagick do
36
    gem "mini_magick"
37 37
  end
38 38

  
39 39
  # Optional Markdown support, not for JRuby
app/controllers/admin_controller.rb
74 74
      [:text_default_administrator_account_changed, User.default_admin_account_changed?],
75 75
      [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
76 76
      ["#{l :text_plugin_assets_writable} (./public/plugin_assets)",   File.writable?(Redmine::Plugin.public_directory)],
77
      [:text_rmagick_available,        Object.const_defined?(:Magick)],
77
      [:text_minimagick_available,     Object.const_defined?(:MiniMagick)],
78 78
      [:text_convert_available,        Redmine::Thumbnail.convert_available?]
79 79
    ]
80 80
  end
appveyor.yml
13 13
build: off
14 14

  
15 15
test_script:
16
  - bundle install --without rmagick
16
  - bundle install --without minimagick
17 17
  - set SCMS=mercurial
18 18
  - set RUN_ON_NOT_OFFICIAL=
19 19
  - set RUBY_VER=1.9
config/configuration.yml.example
179 179
  # the ImageMagick's `convert` binary. Used to generate attachment thumbnails.
180 180
  #imagemagick_convert_command:
181 181

  
182
  # Configuration of RMagick font.
182
  # Configuration of MiniMagick font.
183 183
  #
184
  # Redmine uses RMagick in order to export gantt png.
185
  # You don't need this setting if you don't install RMagick.
184
  # Redmine uses MiniMagick in order to export gantt png.
185
  # You don't need this setting if you don't install MiniMagick.
186 186
  #
187 187
  # In CJK (Chinese, Japanese and Korean),
188 188
  # in order to show CJK characters correctly,
......
195 195
  #
196 196
  # Examples for Japanese:
197 197
  #   Windows:
198
  #     rmagick_font_path: C:\windows\fonts\msgothic.ttc
198
  #     minimagick_font_path: C:\windows\fonts\msgothic.ttc
199 199
  #   Linux:
200
  #     rmagick_font_path: /usr/share/fonts/ipa-mincho/ipam.ttf
200
  #     minimagick_font_path: /usr/share/fonts/ipa-mincho/ipam.ttf
201 201
  #
202
  rmagick_font_path:
202
  minimagick_font_path:
203 203

  
204 204
  # Maximum number of simultaneous AJAX uploads
205 205
  #max_concurrent_ajax_uploads: 2
config/locales/en.yml
1152 1152
  text_default_administrator_account_changed: Default administrator account changed
1153 1153
  text_file_repository_writable: Attachments directory writable
1154 1154
  text_plugin_assets_writable: Plugin assets directory writable
1155
  text_rmagick_available: RMagick available (optional)
1155
  text_minimagick_available: MiniMagick available (optional)
1156 1156
  text_convert_available: ImageMagick convert available (optional)
1157 1157
  text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
1158 1158
  text_destroy_time_entries: Delete reported hours
lib/redmine.rb
18 18
require 'redmine/core_ext'
19 19

  
20 20
begin
21
  require 'rmagick' unless Object.const_defined?(:Magick)
21
  require 'mini_magick' unless Object.const_defined?(:MiniMagick)
22 22
rescue LoadError
23
  # RMagick is not available
23
  # MiniMagick is not available
24 24
end
25 25
begin
26 26
  require 'redcarpet' unless Object.const_defined?(:Redcarpet)
lib/redmine/helpers/gantt.rb
335 335
      end
336 336

  
337 337
      # Generates a gantt image
338
      # Only defined if RMagick is avalaible
338
      # Only defined if MiniMagick is avalaible
339 339
      def to_image(format='PNG')
340 340
        date_to = (@date_from >> @months) - 1
341 341
        show_weeks = @zoom > 1
......
348 348
        g_height = 20 * number_of_rows + 30
349 349
        headers_height = (show_weeks ? 2 * header_height : header_height)
350 350
        height = g_height + headers_height
351
        imgl = Magick::ImageList.new
352
        imgl.new_image(subject_width + g_width + 1, height)
353
        gc = Magick::Draw.new
354
        gc.font = Redmine::Configuration['rmagick_font_path'] || ""
355
        # Subjects
356
        gc.stroke('transparent')
357
        subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
358
        # Months headers
359
        month_f = @date_from
360
        left = subject_width
361
        @months.times do
362
          width = ((month_f >> 1) - month_f) * zoom
363
          gc.fill('white')
364
          gc.stroke('grey')
365
          gc.stroke_width(1)
366
          gc.rectangle(left, 0, left + width, height)
367
          gc.fill('black')
351
        Rails.logger.warn('rmagick_font_path option is deprecated. use minimagick_font_path instead.') \
352
          unless Redmine::Configuration['rmagick_font_path'].nil?
353
        font_path = Redmine::Configuration['minimagick_font_path'].presence || Redmine::Configuration['rmagick_font_path'].presence
354
        img = MiniMagick::Image.create(".#{format}", false)
355
        MiniMagick::Tool::Convert.new do |gc|
356
          gc.size('%dx%d' % [subject_width + g_width + 1, height])
357
          gc.xc('white')
358
          gc.font(font_path) if font_path.present?
359
          # Subjects
368 360
          gc.stroke('transparent')
369
          gc.stroke_width(1)
370
          gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
371
          left = left + width
372
          month_f = month_f >> 1
373
        end
374
        # Weeks headers
375
        if show_weeks
361
          subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
362
          # Months headers
363
          month_f = @date_from
376 364
          left = subject_width
377
          height = header_height
378
          if @date_from.cwday == 1
379
            # date_from is monday
380
            week_f = date_from
381
          else
382
            # find next monday after date_from
383
            week_f = @date_from + (7 - @date_from.cwday + 1)
384
            width = (7 - @date_from.cwday + 1) * zoom
385
            gc.fill('white')
386
            gc.stroke('grey')
387
            gc.stroke_width(1)
388
            gc.rectangle(left, header_height, left + width, 2 * header_height + g_height - 1)
389
            left = left + width
390
          end
391
          while week_f <= date_to
392
            width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
365
          @months.times do
366
            width = ((month_f >> 1) - month_f) * zoom
393 367
            gc.fill('white')
394 368
            gc.stroke('grey')
395
            gc.stroke_width(1)
396
            gc.rectangle(left.round, header_height, left.round + width, 2 * header_height + g_height - 1)
369
            gc.strokewidth(1)
370
            gc.draw('rectangle %d,%d %d,%d' % [
371
              left, 0, left + width, height
372
            ])
397 373
            gc.fill('black')
398 374
            gc.stroke('transparent')
399
            gc.stroke_width(1)
400
            gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
375
            gc.strokewidth(1)
376
            gc.draw('text %d,%d %s' % [
377
              left.round + 8, 14, Redmine::Utils::Shell::shell_quote("#{month_f.year}-#{month_f.month}")
378
            ])
401 379
            left = left + width
402
            week_f = week_f + 7
380
            month_f = month_f >> 1
381
          end
382
          # Weeks headers
383
          if show_weeks
384
            left = subject_width
385
            height = header_height
386
            if @date_from.cwday == 1
387
              # date_from is monday
388
              week_f = date_from
389
            else
390
              # find next monday after date_from
391
              week_f = @date_from + (7 - @date_from.cwday + 1)
392
              width = (7 - @date_from.cwday + 1) * zoom
393
              gc.fill('white')
394
              gc.stroke('grey')
395
              gc.strokewidth(1)
396
              gc.draw('rectangle %d,%d %d,%d' % [
397
                left, header_height, left + width, 2 * header_height + g_height - 1
398
              ])
399
              left = left + width
400
            end
401
            while week_f <= date_to
402
              width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
403
              gc.fill('white')
404
              gc.stroke('grey')
405
              gc.strokewidth(1)
406
              gc.draw('rectangle %d,%d %d,%d' % [
407
                left.round, header_height, left.round + width, 2 * header_height + g_height - 1
408
              ])
409
              gc.fill('black')
410
              gc.stroke('transparent')
411
              gc.strokewidth(1)
412
              gc.draw('text %d,%d %s' % [
413
                left.round + 2, header_height + 14, Redmine::Utils::Shell::shell_quote(week_f.cweek.to_s)
414
              ])
415
              left = left + width
416
              week_f = week_f + 7
417
            end
403 418
          end
404
        end
405
        # Days details (week-end in grey)
406
        if show_days
407
          left = subject_width
408
          height = g_height + header_height - 1
409
          wday = @date_from.cwday
410
          (date_to - @date_from + 1).to_i.times do
411
            width =  zoom
412
            gc.fill(non_working_week_days.include?(wday) ? '#eee' : 'white')
413
            gc.stroke('#ddd')
414
            gc.stroke_width(1)
415
            gc.rectangle(left, 2 * header_height, left + width, 2 * header_height + g_height - 1)
416
            left = left + width
417
            wday = wday + 1
418
            wday = 1 if wday > 7
419
          # Days details (week-end in grey)
420
          if show_days
421
            left = subject_width
422
            height = g_height + header_height - 1
423
            wday = @date_from.cwday
424
            (date_to - @date_from + 1).to_i.times do
425
              width =  zoom
426
              gc.fill(non_working_week_days.include?(wday) ? '#eee' : 'white')
427
              gc.stroke('#ddd')
428
              gc.strokewidth(1)
429
              gc.draw('rectangle %d,%d %d,%d' % [
430
                left, 2 * header_height, left + width, 2 * header_height + g_height - 1
431
              ])
432
              left = left + width
433
              wday = wday + 1
434
              wday = 1 if wday > 7
435
            end
419 436
          end
420
        end
421
        # border
422
        gc.fill('transparent')
423
        gc.stroke('grey')
424
        gc.stroke_width(1)
425
        gc.rectangle(0, 0, subject_width + g_width, headers_height)
426
        gc.stroke('black')
427
        gc.rectangle(0, 0, subject_width + g_width, g_height + headers_height - 1)
428
        # content
429
        top = headers_height + 20
430
        gc.stroke('transparent')
431
        lines(:image => gc, :top => top, :zoom => zoom,
432
              :subject_width => subject_width, :format => :image)
433
        # today red line
434
        if User.current.today >= @date_from and User.current.today <= date_to
435
          gc.stroke('red')
436
          x = (User.current.today - @date_from + 1) * zoom + subject_width
437
          gc.line(x, headers_height, x, headers_height + g_height - 1)
438
        end
439
        gc.draw(imgl)
440
        imgl.format = format
441
        imgl.to_blob
442
      end if Object.const_defined?(:Magick)
437
          # border
438
          gc.fill('transparent')
439
          gc.stroke('grey')
440
          gc.strokewidth(1)
441
          gc.draw('rectangle %d,%d %d,%d' % [
442
            0, 0, subject_width + g_width, headers_height
443
          ])
444
          gc.stroke('black')
445
          gc.draw('rectangle %d,%d %d,%d' % [
446
            0, 0, subject_width + g_width, g_height + headers_height - 1
447
          ])
448
          # content
449
          top = headers_height + 20
450
          gc.stroke('transparent')
451
          lines(:image => gc, :top => top, :zoom => zoom,
452
                :subject_width => subject_width, :format => :image)
453
          # today red line
454
          if User.current.today >= @date_from and User.current.today <= date_to
455
            gc.stroke('red')
456
            x = (User.current.today - @date_from + 1) * zoom + subject_width
457
            gc.draw('line %g,%g %g,%g' % [
458
              x, headers_height, x, headers_height + g_height - 1
459
            ])
460
          end
461
          gc << img.path
462
        end
463
        img.to_blob
464
      ensure
465
        img.destroy! if img
466
      end if Object.const_defined?(:MiniMagick)
443 467

  
444 468
      def to_pdf
445 469
        pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
......
735 759
      def image_subject(params, subject, options={})
736 760
        params[:image].fill('black')
737 761
        params[:image].stroke('transparent')
738
        params[:image].stroke_width(1)
739
        params[:image].text(params[:indent], params[:top] + 2, subject)
762
        params[:image].strokewidth(1)
763
        params[:image].draw('text %d,%d %s' % [
764
          params[:indent], params[:top] + 2, Redmine::Utils::Shell::shell_quote(subject)
765
        ])
740 766
      end
741 767

  
742 768
      def issue_relations(issue)
......
912 938
        # Renders the task bar, with progress and late
913 939
        if coords[:bar_start] && coords[:bar_end]
914 940
          params[:image].fill('#aaa')
915
          params[:image].rectangle(params[:subject_width] + coords[:bar_start],
916
                                   params[:top],
917
                                   params[:subject_width] + coords[:bar_end],
918
                                   params[:top] - height)
941
          params[:image].draw('rectangle %d,%d %d,%d' % [
942
            params[:subject_width] + coords[:bar_start],
943
            params[:top],
944
            params[:subject_width] + coords[:bar_end],
945
            params[:top] - height
946
          ])
919 947
          if coords[:bar_late_end]
920 948
            params[:image].fill('#f66')
921
            params[:image].rectangle(params[:subject_width] + coords[:bar_start],
922
                                     params[:top],
923
                                     params[:subject_width] + coords[:bar_late_end],
924
                                     params[:top] - height)
949
            params[:image].draw('rectangle %d,%d %d,%d' % [
950
              params[:subject_width] + coords[:bar_start],
951
              params[:top],
952
              params[:subject_width] + coords[:bar_late_end],
953
              params[:top] - height
954
            ])
925 955
          end
926 956
          if coords[:bar_progress_end]
927 957
            params[:image].fill('#00c600')
928
            params[:image].rectangle(params[:subject_width] + coords[:bar_start],
929
                                     params[:top],
930
                                     params[:subject_width] + coords[:bar_progress_end],
931
                                     params[:top] - height)
958
            params[:image].draw('rectangle %d,%d %d,%d' % [
959
              params[:subject_width] + coords[:bar_start],
960
              params[:top],
961
              params[:subject_width] + coords[:bar_progress_end],
962
              params[:top] - height
963
            ])
932 964
          end
933 965
        end
934 966
        # Renders the markers
......
937 969
            x = params[:subject_width] + coords[:start]
938 970
            y = params[:top] - height / 2
939 971
            params[:image].fill('blue')
940
            params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
972
            params[:image].draw('polygon %d,%d %d,%d %d,%d %d,%d' % [
973
              x - 4, y,
974
              x, y - 4,
975
              x + 4, y,
976
              x, y + 4
977
            ])
941 978
          end
942 979
          if coords[:end]
943 980
            x = params[:subject_width] + coords[:end] + params[:zoom]
944 981
            y = params[:top] - height / 2
945 982
            params[:image].fill('blue')
946
            params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
983
            params[:image].draw('polygon %d,%d %d,%d %d,%d %d,%d' % [
984
              x - 4, y,
985
              x, y - 4,
986
              x + 4, y,
987
              x, y + 4
988
            ])
947 989
          end
948 990
        end
949 991
        # Renders the label on the right
950 992
        if label
951 993
          params[:image].fill('black')
952
          params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,
953
                              params[:top] + 1,
954
                              label)
994
          params[:image].draw('text %d,%d %s' % [
995
            params[:subject_width] + (coords[:bar_end] || 0) + 5, params[:top] + 1, Redmine::Utils::Shell::shell_quote(label)
996
          ])
955 997
        end
956 998
      end
957 999
    end
test/functional/gantts_controller_test.rb
149 149
    assert @response.body.starts_with?('%PDF')
150 150
  end
151 151

  
152
  if Object.const_defined?(:Magick)
152
  if Object.const_defined?(:MiniMagick)
153 153
    def test_gantt_should_export_to_png
154 154
      get :show, :params => {
155 155
          :project_id => 1,
(1-1/4)