From 0c2070da0076c3f786c409f67afc132990255994 Mon Sep 17 00:00:00 2001 From: Martin Corino Date: Sun, 7 Apr 2024 19:46:56 +0200 Subject: [PATCH 1/6] fix get-/set_dashes docs --- lib/wx/core/graphics_pen_info.rb | 18 ++++++++++++++++ lib/wx/doc/graphics_context.rb | 14 ++++++++++++- lib/wx/doc/pen.rb | 26 ++++++++++++++++++++++++ rakelib/lib/director/graphics_context.rb | 2 +- rakelib/lib/director/pen.rb | 2 +- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 lib/wx/core/graphics_pen_info.rb diff --git a/lib/wx/core/graphics_pen_info.rb b/lib/wx/core/graphics_pen_info.rb new file mode 100644 index 00000000..7a7850f4 --- /dev/null +++ b/lib/wx/core/graphics_pen_info.rb @@ -0,0 +1,18 @@ +# Copyright (c) 2023 M.J.N. Corino, The Netherlands +# +# This software is released under the MIT license. +# +# Some parts are +# Copyright 2004-2007, wxRuby development team +# released under the MIT-like wxRuby2 license + +class Wx::GraphicsPenInfo + + # make Wx::GraphicsPenInfo#dashes return self + wx_dashes = instance_method :dashes + define_method :dashes do |*args| + wx_dashes.bind(self).call(*args) + self + end + +end diff --git a/lib/wx/doc/graphics_context.rb b/lib/wx/doc/graphics_context.rb index 7fd585fb..ca0a780e 100644 --- a/lib/wx/doc/graphics_context.rb +++ b/lib/wx/doc/graphics_context.rb @@ -56,7 +56,19 @@ class GraphicsGradientStop # @param col [Wx::Colour] The colour of this stop. Note that the alpha component of the colour is honoured thus allowing the background colours to partially show through the gradient. # @param pos [Float] The stop position, must be in [0, 1] range with 0 being the beginning and 1 the end of the gradient. # @return [Wx::GraphicsGradientStop] - def initialize(col=Wx::TransparentColour, pos=0.0) end + def initialize(col=Wx::TRANSPARENT_COLOUR, pos=0.0) end + + end + + class GraphicsPenInfo + + # @param dashes [Array] + # @return [Wx::GraphicsPenInfo] + def dashes(dashes) end + + # @return [Array] + def get_dashes; end + alias_method :dashes, :get_dashes end diff --git a/lib/wx/doc/pen.rb b/lib/wx/doc/pen.rb index 70fac584..f8ed6cca 100644 --- a/lib/wx/doc/pen.rb +++ b/lib/wx/doc/pen.rb @@ -16,8 +16,34 @@ class Pen # @return [Wx::Pen] def self.find_or_create_pen(colour, width=1, style=Wx::PenStyle::PENSTYLE_SOLID) end + # Associates an array of dash values with the pen. + # + # @see Wx::Pen#get_dashes + # @param dashes [Array] + # @return [void] + def set_dashes(dashes) end + + # Gets an array of dashes. + # + # @see Wx::Pen#set_dashes + # @return [Array] + def get_dashes; end + alias_method :dashes, :get_dashes + end ThePenList = Wx::Pen + class PenInfo + + # @param dashes [Array] + # @return [Wx::PenInfo] + def dashes(dashes) end + + # @return [Array] + def get_dashes; end + alias_method :dashes, :get_dashes + + end + end diff --git a/rakelib/lib/director/graphics_context.rb b/rakelib/lib/director/graphics_context.rb index 0877e606..61d1c543 100644 --- a/rakelib/lib/director/graphics_context.rb +++ b/rakelib/lib/director/graphics_context.rb @@ -56,7 +56,7 @@ def setup # dealt with below - these require special handling because of the use # of wxDash array, which cannot be freed until the peninfo is disposed of # or until a new dash pattern is specified. - spec.ignore(%w[wxGraphicsPenInfo::GetDashes wxGraphicsPenInfo::Dashes], ignore_doc: false) + spec.ignore(%w[wxGraphicsPenInfo::GetDashes wxGraphicsPenInfo::Dashes]) spec.ignore 'wxGraphicsPenInfo::GetDash' spec.add_extend_code 'wxGraphicsPenInfo', <<~__HEREDOC // Returns a ruby array with the dash lengths diff --git a/rakelib/lib/director/pen.rb b/rakelib/lib/director/pen.rb index 0da70367..7d8b3325 100644 --- a/rakelib/lib/director/pen.rb +++ b/rakelib/lib/director/pen.rb @@ -79,7 +79,7 @@ def setup # dealt with below - these require special handling because of the use # of wxDash array, which cannot be freed until the pen(info) is disposed of # or until a new dash pattern is specified. - spec.ignore(%w[wxPen::GetDashes wxPen::SetDashes wxPenInfo::GetDashes wxPenInfo::Dashes], ignore_doc: false) + spec.ignore(%w[wxPen::GetDashes wxPen::SetDashes wxPenInfo::GetDashes wxPenInfo::Dashes]) spec.ignore 'wxPenInfo::GetDash' spec.add_extend_code 'wxPen', <<~__HEREDOC // Returns a ruby array with the dash lengths From 28765888e2ca545f615cc466be238532a9afafd4 Mon Sep 17 00:00:00 2001 From: Martin Corino Date: Wed, 10 Apr 2024 17:12:32 +0200 Subject: [PATCH 2/6] fix argument conversion for Wx::DC#draw_poly_polygon --- rakelib/lib/typemap/points_list.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rakelib/lib/typemap/points_list.rb b/rakelib/lib/typemap/points_list.rb index e07fc368..007358ec 100644 --- a/rakelib/lib/typemap/points_list.rb +++ b/rakelib/lib/typemap/points_list.rb @@ -47,7 +47,8 @@ module PointsList } else { - rb_raise(rb_eTypeError, "Wrong type for wxPoint parameter %i", i); + VALUE str = rb_inspect(rb_item); + rb_raise(rb_eTypeError, "Wrong type for wxPoint parameter %i (%s)", i, StringValuePtr(str)); } } } @@ -101,7 +102,12 @@ module PointsList // array of all the points VALUE all_points = rb_funcall($input, rb_intern("flatten"), 0); point_arr = std::make_unique(RARRAY_LEN(all_points)); - wxRuby_PointArrayRubyToC(all_points, point_arr.get()); + wxPoint* point_ptr = point_arr.get(); + for ( int i = 0; i < RARRAY_LEN($input); i++ ) + { + wxRuby_PointArrayRubyToC(rb_ary_entry($input, i), point_ptr); + point_ptr += count_arr[i]; + } $3 = point_arr.get(); } __CODE From 51f32e19eebc736a8524a5b70f8afb5405102fd7 Mon Sep 17 00:00:00 2001 From: Martin Corino Date: Sat, 13 Apr 2024 18:30:46 +0200 Subject: [PATCH 3/6] add missing global methods --- lib/wx/core/geometry.rb | 9 +++++++++ lib/wx/doc/geometry.rb | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/wx/core/geometry.rb b/lib/wx/core/geometry.rb index 35fe6673..77862701 100644 --- a/lib/wx/core/geometry.rb +++ b/lib/wx/core/geometry.rb @@ -3,6 +3,15 @@ # This software is released under the MIT license. module Wx + + def self.deg_to_rad(deg) + (deg * Math::PI) / 180.0 + end + + def self.rad_to_deg(rad) + (rad * 180.0) / Math::PI + end + class Point2DInt alias :x :get_x diff --git a/lib/wx/doc/geometry.rb b/lib/wx/doc/geometry.rb index b0de3591..0ec9232c 100644 --- a/lib/wx/doc/geometry.rb +++ b/lib/wx/doc/geometry.rb @@ -11,6 +11,17 @@ module Wx + + # Convert degrees to radians. + # @param [Float] deg degrees + # @return [Float] radians + def self.deg_to_rad(deg) end + + # Convert radians to degrees. + # @param [Float] rad radians + # @return [Float] degrees + def self.rad_to_deg(rad) end + class Point2DInt # @return [Integer] From c0f3fd4045761f8a315cd75bf2358ae6933639d2 Mon Sep 17 00:00:00 2001 From: Martin Corino Date: Sat, 13 Apr 2024 18:31:29 +0200 Subject: [PATCH 4/6] fix and complete HSVValue and RGBValue --- lib/wx/core/image.rb | 49 +++++++++++++++++++++++++++++ lib/wx/doc/image.rb | 52 ++++++++++++++++++++++++++++++ rakelib/lib/director/image.rb | 59 +++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/lib/wx/core/image.rb b/lib/wx/core/image.rb index f68ca236..da1584e6 100644 --- a/lib/wx/core/image.rb +++ b/lib/wx/core/image.rb @@ -108,6 +108,55 @@ def find_first_unused_colour(r=1, g=0, b=0) hist_hash.extend Histogram hist_hash end + + # import flattened nested classes + HSVValue = Wx::HSVValue + RGBValue = Wx::RGBValue + + end + + class HSVValue + + # More informative output when converted to string + def to_s + "#" + end + + def inspect + to_s + end + + # make HSVValue usable for parallel assignments like `x, y = pt` + def to_ary + [self.hue, self.saturation, self.value] + end + + def to_rgb + Image::hsv_to_rgb(self) + end + + end + + class RGBValue + + # More informative output when converted to string + def to_s + "#" + end + + def inspect + to_s + end + + # make RGBValue usable for parallel assignments like `x, y = pt` + def to_ary + [self.red, self.green, self.blue] + end + + def to_hsv + Image::rgb_to_hsv(self) + end + end def self.Image(name, bmp_type = nil, *rest, art_path: nil, art_section: nil) diff --git a/lib/wx/doc/image.rb b/lib/wx/doc/image.rb index 41b9183f..7f877a0d 100644 --- a/lib/wx/doc/image.rb +++ b/lib/wx/doc/image.rb @@ -51,6 +51,58 @@ def find_first_unused_colour(r=1, g=0, b=0) end # @return [Hash] hash object extended with {Wx::Image::Histogram} def compute_histogram; end + # Converts a color in HSV colour space to RGB colour space. + # @param [Wx::Image::HSVValue,Array(Float,Float,Float)] arg HSV colour + def self.hsv_to_rgb(arg) end + + # Converts a color in RGB colour space to HSV colour space. + # @param [Wx::Image::RGBValue,Array(Float,Float,Float)] arg RGB colour + def self.rgb_to_hsv(arg) end + + class HSVValue + + # Constructor for HSVValue, an object that contains values for hue, saturation and value which represent the value of a color. + # It is used by {Wx::Image.hsv_to_rgb} and {Wx::Image.rgb_to_hsv}, which convert between HSV color space and RGB color space. + # @param [Float] hue + # @param [Float] saturation + # @param [Float] value + # @return [Wx::Image::HSVValue] + def initialize(hue, saturation, value)end + + attr :hue, :saturation, :value + + # Make HSVValue usable for parallel assignments like `hue, saturation, value = hsv` + # @return [Array(Float,Float,Float)] + def to_ary; end + + # Convert to {Wx::Image::RGBValue} + # @return [Wx::Image::RGBValue] + def to_rgb; end + + end + + class RGBValue + + # Constructor for RGBValue, an object that contains values for red, green and blue which represent the value of a color. + # It is used by {Wx::Image.hsv_to_rgb} and {Wx::Image.rgb_to_hsv}, which convert between RGB color space and HSV color space. + # @param [Float] red + # @param [Float] green + # @param [Float] blue + # @return [Wx::Image::RGBValue] + def initialize(red, green, blue)end + + attr :red, :green, :blue + + # Make RGBValue usable for parallel assignments like `red, green, blue = rgb` + # @return [Array(Float,Float,Float)] + def to_ary; end + + # Convert to {Wx::Image::HSVValue} + # @return [Wx::Image::HSVValue] + def to_hsv; end + + end + end # @!group Art creation methods diff --git a/rakelib/lib/director/image.rb b/rakelib/lib/director/image.rb index a55ec91c..5ed737d2 100644 --- a/rakelib/lib/director/image.rb +++ b/rakelib/lib/director/image.rb @@ -212,6 +212,65 @@ def setup return rb_img_hist; } __HEREDOC + # make sure to wrap the public attributes + spec.regard 'wxImage::HSVValue::hue', + 'wxImage::HSVValue::saturation', + 'wxImage::HSVValue::value', + 'wxImage::RGBValue::red', + 'wxImage::RGBValue::green', + 'wxImage::RGBValue::blue' + # ignore these and add customs + spec.ignore 'wxImage::HSVtoRGB', + 'wxImage::RGBtoHSV' + spec.add_extend_code 'wxImage', <<~__HEREDOC + static wxImage::RGBValue hsv_to_rgb(VALUE arg) + { + std::unique_ptr tmp; + wxImage::HSVValue* p_hsv; + if ( TYPE(arg) == T_DATA ) + { + void* argp; + SWIG_ConvertPtr(arg, &argp, SWIGTYPE_p_wxImage__HSVValue, 0); + p_hsv = reinterpret_cast< wxImage::HSVValue * >(argp); + } + else if ( TYPE(arg) == T_ARRAY && RARRAY_LEN(arg) == 3 ) + { + p_hsv = new wxImage::HSVValue( NUM2DBL( rb_ary_entry(arg, 0) ), + NUM2DBL( rb_ary_entry(arg, 1) ), + NUM2DBL( rb_ary_entry(arg, 2) ) ); + tmp.reset(p_hsv); // auto destruct when method scope ends + } + else + { + rb_raise(rb_eArgError, "Expected either Array(Float,Float,Float) or Wx::Image::HSVValue for #0"); + } + return wxImage::HSVtoRGB(*p_hsv); + } + + static wxImage::HSVValue rgb_to_hsv(VALUE arg) + { + std::unique_ptr tmp; + wxImage::RGBValue* p_rgb; + if ( TYPE(arg) == T_DATA ) + { + void* argp; + SWIG_ConvertPtr(arg, &argp, SWIGTYPE_p_wxImage__RGBValue, 0); + p_rgb = reinterpret_cast< wxImage::RGBValue * >(argp); + } + else if ( TYPE(arg) == T_ARRAY && RARRAY_LEN(arg) == 3 ) + { + p_rgb = new wxImage::RGBValue( NUM2DBL( rb_ary_entry(arg, 0) ), + NUM2DBL( rb_ary_entry(arg, 1) ), + NUM2DBL( rb_ary_entry(arg, 2) ) ); + tmp.reset(p_rgb); // auto destruct when method scope ends + } + else + { + rb_raise(rb_eArgError, "Expected either Array(Float,Float,Float) or Wx::Image::RGBValue for #0"); + } + return wxImage::RGBtoHSV(*p_rgb); + } + __HEREDOC spec.do_not_generate(:functions) end end # class Image From 8854326e60eb969783a0c30f4fa970f50410e384 Mon Sep 17 00:00:00 2001 From: Martin Corino Date: Wed, 17 Apr 2024 11:17:36 +0200 Subject: [PATCH 5/6] fix Wx::SystemSettings class methods --- rakelib/lib/director/system_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rakelib/lib/director/system_settings.rb b/rakelib/lib/director/system_settings.rb index 5f127b66..43807c72 100644 --- a/rakelib/lib/director/system_settings.rb +++ b/rakelib/lib/director/system_settings.rb @@ -22,7 +22,7 @@ def setup 'wxSystemSettings::HasFeature', 'wxSystemSettings::GetScreenType' spec.ignore 'wxSystemSettings::GetAppearance' - spec.add_extend_code <<~__HEREDOC + spec.add_extend_code 'wxSystemSettings', <<~__HEREDOC static wxString GetAppearanceName() { return wxSystemSettings::GetAppearance().GetName(); From 63bcf808d54f74380a79ee9a8d597888103751af Mon Sep 17 00:00:00 2001 From: Martin Corino Date: Wed, 17 Apr 2024 11:18:32 +0200 Subject: [PATCH 6/6] add large 2d drawing sample --- samples/drawing/drawing.rb | 2276 ++++++++++++++++++++++++++++++++ samples/drawing/tn_drawing.png | Bin 0 -> 33630 bytes 2 files changed, 2276 insertions(+) create mode 100644 samples/drawing/drawing.rb create mode 100644 samples/drawing/tn_drawing.png diff --git a/samples/drawing/drawing.rb b/samples/drawing/drawing.rb new file mode 100644 index 00000000..22169144 --- /dev/null +++ b/samples/drawing/drawing.rb @@ -0,0 +1,2276 @@ +# Copyright (c) 2023 M.J.N. Corino = self.next_id The Netherlands +# +# This software is released under the MIT license. +# +# Adapted for wxRuby from wxWidgets widgets sample +# Copyright (c) Robert Roebling + +require 'wx' + +module Drawing + + DRAWING_DC_SUPPORTS_ALPHA = %w[WXGTK WXOSX].include?(Wx::PLATFORM) + + module ID + include Wx::IDHelper + + # menu items + File_Quit = Wx::ID_EXIT + File_About = Wx::ID_ABOUT + + MenuShow_First = self.next_id(Wx::ID_HIGHEST) + File_ShowDefault = MenuShow_First + File_ShowText = self.next_id + File_ShowLines = self.next_id + File_ShowBrushes = self.next_id + File_ShowPolygons = self.next_id + File_ShowMask = self.next_id + File_ShowMaskStretch = self.next_id + File_ShowOps = self.next_id + File_ShowRegions = self.next_id + File_ShowCircles = self.next_id + File_ShowSplines = self.next_id + File_ShowAlpha = self.next_id + File_ShowGraphics = self.next_id + File_ShowSystemColours = self.next_id + File_ShowDatabaseColours = self.next_id + File_ShowGradients = self.next_id + MenuShow_Last = File_ShowGradients + + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + File_DC = self.next_id + File_GC_Default = self.next_id + if Wx.has_feature?(:USE_CAIRO) + File_GC_Cairo = self.next_id + end # USE_CAIRO + if Wx::PLATFORM == 'WXMSW' + if Wx.has_feature?(:USE_GRAPHICS_GDIPLUS) + File_GC_GDIPlus = self.next_id + end + if Wx.has_feature?(:USE_GRAPHICS_DIRECT2D) + File_GC_Direct2D = self.next_id + end + end # WXMSW + end # USE_GRAPHICS_CONTEXT + File_BBox = self.next_id + File_Clip = self.next_id + File_Buffer = self.next_id + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + File_AntiAliasing = self.next_id + end + File_Copy = self.next_id + File_Save = self.next_id + + MenuOption_First = self.next_id + + MapMode_Text = MenuOption_First + MapMode_Lometric = self.next_id + MapMode_Twips = self.next_id + MapMode_Points = self.next_id + MapMode_Metric = self.next_id + + UserScale_StretchHoriz = self.next_id + UserScale_ShrinkHoriz = self.next_id + UserScale_StretchVertic = self.next_id + UserScale_ShrinkVertic = self.next_id + UserScale_Restore = self.next_id + + AxisMirror_Horiz = self.next_id + AxisMirror_Vertic = self.next_id + + LogicalOrigin_MoveDown = self.next_id + LogicalOrigin_MoveUp = self.next_id + LogicalOrigin_MoveLeft = self.next_id + LogicalOrigin_MoveRight = self.next_id + LogicalOrigin_Set = self.next_id + LogicalOrigin_Restore = self.next_id + + TransformMatrix_Set = self.next_id + TransformMatrix_Reset = self.next_id + + Colour_TextForeground = self.next_id + Colour_TextBackground = self.next_id + Colour_Background = self.next_id + Colour_BackgroundMode = self.next_id + Colour_TextureBackground = self.next_id + + MenuOption_Last = Colour_TextureBackground + end + + class MyCanvas < Wx::ScrolledWindow + + class DrawMode < Wx::Enum + Draw_Normal = self.new(0) + Draw_Stretch = self.new(1) + end + + def initialize(parent) + super(parent, Wx::ID_ANY, style: Wx::HSCROLL | Wx::VSCROLL) + + @owner = parent + @show = ID::File_ShowDefault + @smile_bmp = Wx.Bitmap(:smile) + @std_icon = Wx::ArtProvider.get_icon(Wx::ART_INFORMATION) + @clip = false + @rubberBand = false + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + @renderer = nil + @useAntiAliasing = true + end + @useBuffer = false + @showBBox = false + @sizeDIP = Wx::Size.new + @currentpoint = Wx::Point.new + @anchorpoint = Wx::Point.new + @overlay = Wx::Overlay.new + + evt_paint :on_paint + evt_motion :on_mouse_move + evt_left_down :on_mouse_down + evt_left_up :on_mouse_up + end + + def on_paint(_event) + if @useBuffer + Wx::BufferedPaintDC.draw_on(self) { |bpdc| draw(bpdc) } + else + self.paint { |pdc| draw(pdc) } + end + end + + def on_mouse_move(event) + if Wx.has_feature?(:USE_STATUSBAR) + Wx::ClientDC.draw_on(self) do |dc| + prepare_dc(dc) + @owner.prepare_dc(dc) + + pos = dc.device_to_logical(event.position) + dipPos = dc.to_dip(pos) + str = "Mouse position: #{pos.x},#{pos.y}" + str << " DIP position: #{dipPos.x},#{dipPos.y}" if pos != dipPos + @owner.set_status_text(str) + end + + if @rubberBand + @currentpoint = calc_unscrolled_position(event.position) + newrect = Wx::Rect.new(@anchorpoint, @currentpoint) + + Wx::ClientDC.draw_on(self) do |dc| + prepare_dc(dc) + + Wx::DCOverlay.draw_on(@overlay, dc) { |overlaydc| overlaydc.clear } + + if Wx::PLATFORM == 'WXMAC' + dc.set_pen(Wx::GREY_PEN ) + dc.set_brush(Wx::Brush.new(Wx::Colour.new(192,192,192,64))) + else + dc.set_pen(Wx::Pen.new(Wx::LIGHT_GREY, 2)) + dc.set_brush(Wx::TRANSPARENT_BRUSH) + end + dc.draw_rectangle(newrect) + end + end + end # USE_STATUSBAR + end + + def on_mouse_down(event) + @anchorpoint = calc_unscrolled_position(event.position) + @currentpoint = @anchorpoint + @rubberBand = true + capture_mouse + end + + def on_mouse_up(event) + if @rubberBand + release_mouse + Wx::ClientDC.draw_on(self) do |dc| + prepare_dc(dc) + Wx::DCOverlay.draw_on(@overlay, dc) { |overlaydc| overlaydc.clear } + end + @overlay.reset + @rubberBand = false + + endpoint = calc_unscrolled_position(event.position) + + # Don't pop up the message box if nothing was actually selected. + if endpoint != @anchorpoint + Wx.log_message('Selected rectangle from (%d, %d) to (%d, %d)', + @anchorpoint.x, @anchorpoint.y, + endpoint.x, endpoint.y) + end + end + end + + def to_show(show) + @show = show + refresh + end + + def get_page + @show + end + + # set or remove the clipping region + def clip(clip) + @clip = clip + refresh + end + + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + + def has_renderer + !!@renderer + end + + def use_graphic_renderer(renderer) + @renderer = renderer + if renderer + major, minor, micro = renderer.get_version + str = 'Graphics renderer: %s %i.%i.%i' % [renderer.get_name, major, minor, micro] + @owner.set_status_text(str, 1) + else + @owner.set_status_text('', 1) + end + + refresh + end + + def is_default_renderer + return false unless @renderer + @renderer == Wx::GraphicsRenderer.get_default_renderer + end + + def get_renderer + @renderer + end + + def enable_anti_aliasing(use) + @use_anti_aliasing = use + refresh + end + + end # USE_GRAPHICS_CONTEXT + + def use_buffer(use) + @useBuffer = use + refresh + end + + def show_bounding_box(show) + @showBBox = show + refresh + end + + def get_dip_drawing_size + @sizeDIP + end + + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + def draw(pdc) + if @renderer + context = @renderer.create_context(pdc) + + context.set_antialias_mode(@useAntiAliasing ? Wx::ANTIALIAS_DEFAULT : Wx::ANTIALIAS_NONE) + + Wx::GCDC.draw_on do |gdc| + gdc.set_background(Wx::Brush.new(get_background_colour)) + gdc.set_graphics_context(context) + # Adjust scrolled contents for screen drawing operations only. + if pdc.is_a?(Wx::BufferedPaintDC) || pdc.is_a?(Wx::PaintDC) + prepare_dc(pdc) + end + + @owner.prepare_dc(pdc) + + do_draw(gdc) + end + else + # Adjust scrolled contents for screen drawing operations only. + if pdc.is_a?(Wx::BufferedPaintDC) || pdc.is_a?(Wx::PaintDC) + prepare_dc(pdc) + end + + @owner.prepare_dc(pdc) + + do_draw(pdc) + end + end + else + def draw(pdc) + # Adjust scrolled contents for screen drawing operations only. + if pdc.is_a?(Wx::BufferedPaintDC) || pdc.is_a?(Wx::PaintDC) + prepare_dc(pdc) + end + + @owner.prepare_dc(pdc) + + do_draw(pdc) + end + end + + protected + + def do_draw(dc) + dc.set_background_mode(@owner.backgroundMode) + dc.set_background(@owner.backgroundBrush) if @owner.backgroundBrush.ok? + dc.set_text_foreground(@owner.colourForeground) if @owner.colourForeground.ok? + dc.set_text_background(@owner.colourBackground) if @owner.colourBackground.ok? + + if @owner.textureBackground + unless @owner.backgroundBrush.ok? + dc.set_background(Wx::Brush.new(Wx::Colour.new(0, 128, 0))) + end + end + + if @clip + dc.set_clipping_region([dc.from_dip(100), dc.from_dip(100)], + [dc.from_dip(100), dc.from_dip(100)]) + end + + dc.clear + + if @owner.textureBackground + dc.set_pen(Wx::MEDIUM_GREY_PEN) + 200.times { |i| dc.draw_line(0, dc.from_dip(i*10), dc.from_dip(i*10), 0) } + end + + case @show + when ID::File_ShowDefault + draw_default(dc) + + when ID::File_ShowCircles + draw_circles(dc) + + when ID::File_ShowSplines + draw_splines(dc) + + when ID::File_ShowRegions + draw_regions(dc) + + when ID::File_ShowText + draw_text(dc) + + when ID::File_ShowLines + draw_test_lines(0, 100, 0, dc) + draw_test_lines(0, 320, 1, dc) + draw_test_lines(0, 540, 2, dc) + draw_test_lines(0, 760, 6, dc) + draw_cross_hair(0, 0, 400, 90, dc) + + when ID::File_ShowBrushes + draw_test_brushes(dc) + + when ID::File_ShowPolygons + draw_test_poly(dc) + + when ID::File_ShowMask + draw_images(dc, DrawMode::Draw_Normal) + + when ID::File_ShowMaskStretch + draw_images(dc, DrawMode::Draw_Stretch) + + when ID::File_ShowOps + draw_with_logical_ops(dc) + + when ID::File_ShowAlpha + draw_alpha(dc) + + when ID::File_ShowGraphics + draw_graphics(dc.get_graphics_context) if dc.is_a?(Wx::GCDC) + + when ID::File_ShowGradients + draw_gradients(dc) + + when ID::File_ShowSystemColours + draw_system_colours(dc) + + end + + # For drawing with raw Wx::GraphicsContext + # there is no bounding box to obtain. + if @showBBox && !(Wx.has_feature?(:USE_GRAPHICS_CONTEXT) && @show == ID::File_ShowGraphics) + dc.set_pen(Wx::Pen.new(Wx::Colour.new(0, 128, 0), 1, Wx::PENSTYLE_DOT)) + dc.set_brush(Wx::TRANSPARENT_BRUSH) + dc.draw_rectangle(dc.min_x, dc.min_y, dc.max_x-dc.min_x+1, dc.max_y-dc.min_y+1) + end + + # Adjust drawing area dimensions only if screen drawing is invoked. + if dc.is_a?(Wx::BufferedPaintDC) || dc.is_a?(Wx::PaintDC) + x0, y0 = dc.get_device_origin + @sizeDIP.x = dc.to_dip(dc.logical_to_device_x(dc.max_x) - x0) + 1 + @sizeDIP.y = dc.to_dip(dc.logical_to_device_y(dc.max_y) - y0) + 1 + end + end + + def draw_test_lines(x, y, width, dc) + dc.set_pen(Wx::Pen.new( Wx::BLACK, width)) + dc.set_brush(Wx::WHITE_BRUSH) + dc.draw_text("Testing lines of width #{width}", dc.from_dip(x + 10), dc.from_dip(y - 10)) + dc.draw_rectangle(dc.from_dip(x + 10), dc.from_dip(y + 10), dc.from_dip(100), dc.from_dip(190)) + + dc.draw_text("Solid/dot/short dash/long dash/dot dash", dc.from_dip(x + 150), dc.from_dip(y + 10)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width ) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 20), dc.from_dip(100), dc.from_dip(y + 20)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_DOT) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 30), dc.from_dip(100), dc.from_dip(y + 30)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_SHORT_DASH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 40), dc.from_dip(100), dc.from_dip(y + 40)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_LONG_DASH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 50), dc.from_dip(100), dc.from_dip(y + 50)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_DOT_DASH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 60), dc.from_dip(100), dc.from_dip(y + 60)) + + dc.draw_text("Hatches", dc.from_dip(x + 150), dc.from_dip(y + 70)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_BDIAGONAL_HATCH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 70), dc.from_dip(100), dc.from_dip(y + 70)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_CROSSDIAG_HATCH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 80), dc.from_dip(100), dc.from_dip(y + 80)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_FDIAGONAL_HATCH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 90), dc.from_dip(100), dc.from_dip(y + 90)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_CROSS_HATCH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 100), dc.from_dip(100), dc.from_dip(y + 100)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_HORIZONTAL_HATCH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 110), dc.from_dip(100), dc.from_dip(y + 110)) + dc.set_pen( Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_VERTICAL_HATCH) ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 120), dc.from_dip(100), dc.from_dip(y + 120)) + + dc.draw_text("User dash", dc.from_dip(x + 150), dc.from_dip(y + 140)) + ud = Wx::Pen.new( Wx::BLACK, width, Wx::PENSTYLE_USER_DASH ) + dash1 = [ + 8, # Long dash <---------+ + 2, # Short gap | + 3, # Short dash | + 2, # Short gap | + 3, # Short dash | + 2] # Short gap and repeat + + ud.set_dashes(dash1) + dc.set_pen( ud ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 140), dc.from_dip(100), dc.from_dip(y + 140)) + dash1[0] = 5 # Make first dash shorter + ud.set_dashes( dash1 ) + dc.set_pen( ud ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 150), dc.from_dip(100), dc.from_dip(y + 150)) + dash1[2] = 5 # Make second dash longer + ud.set_dashes( dash1 ) + dc.set_pen( ud ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 160), dc.from_dip(100), dc.from_dip(y + 160)) + dash1[4] = 5 # Make third dash longer + ud.set_dashes( dash1 ) + dc.set_pen( ud ) + dc.draw_line(dc.from_dip(x + 20), dc.from_dip(y + 170), dc.from_dip(100), dc.from_dip(y + 170)) + + penWithCap = Wx::Pen.new(Wx::BLACK, width) + dc.set_pen(penWithCap) + dc.draw_text("Default cap", dc.from_dip(x + 270), dc.from_dip(y + 40)) + dc.draw_line(dc.from_dip(x + 200), dc.from_dip(y + 50), dc.from_dip(x + 250), dc.from_dip(y + 50)) + + penWithCap.set_cap(Wx::CAP_BUTT) + dc.set_pen(penWithCap) + dc.draw_text("Butt ", dc.from_dip(x + 270), dc.from_dip(y + 60)) + dc.draw_line(dc.from_dip(x + 200), dc.from_dip(y + 70), dc.from_dip(x + 250), dc.from_dip(y + 70)) + + penWithCap.set_cap(Wx::CAP_ROUND) + dc.set_pen(penWithCap) + dc.draw_text("Round cap", dc.from_dip(x + 270), dc.from_dip(y + 80)) + dc.draw_line(dc.from_dip(x + 200), dc.from_dip(y + 90), dc.from_dip(x + 250), dc.from_dip(y + 90)) + + penWithCap.set_cap(Wx::CAP_PROJECTING) + dc.set_pen(penWithCap) + dc.draw_text("Projecting cap", dc.from_dip(x + 270), dc.from_dip(y + 100)) + dc.draw_line(dc.from_dip(x + 200), dc.from_dip(y + 110), dc.from_dip(x + 250), dc.from_dip(y + 110)) + end + + def draw_cross_hair(x, y, width, height, dc) + dc.draw_text("Cross hair", dc.from_dip(x + 10), dc.from_dip(y + 10)) + dc.set_clipping_region(dc.from_dip(x), dc.from_dip(y), dc.from_dip(width), dc.from_dip(height)) + dc.set_pen(Wx::Pen.new(Wx::BLUE, 2)) + dc.cross_hair(dc.from_dip(x + width / 2), dc.from_dip(y + height / 2)) + dc.destroy_clipping_region + end + + def draw_test_poly(dc) + brushHatch = Wx::Brush.new(Wx::RED, Wx::BRUSHSTYLE_FDIAGONAL_HATCH) + dc.set_brush(brushHatch) + + star = [ + dc.from_dip(Wx::Point.new(100, 60)), + dc.from_dip(Wx::Point.new(60, 150)), + dc.from_dip(Wx::Point.new(160, 100)), + dc.from_dip(Wx::Point.new(40, 100)), + dc.from_dip(Wx::Point.new(140, 150))] + + dc.draw_text("You should see two (irregular) stars below, the left one hatched", + dc.from_dip(10), dc.from_dip(10)) + dc.draw_text("except for the central region and the right one entirely hatched", + dc.from_dip(10), dc.from_dip(30)) + dc.draw_text("The third star only has a hatched outline", dc.from_dip(10), dc.from_dip(50)) + + dc.draw_polygon(star, 0, dc.from_dip(30)) + dc.draw_polygon(star, dc.from_dip(160), dc.from_dip(30), Wx::WINDING_RULE) + + brushHatchGreen = Wx::Brush.new(Wx::GREEN, Wx::BRUSHSTYLE_FDIAGONAL_HATCH) + dc.set_brush(brushHatchGreen) + star2 = [ + [dc.from_dip(Wx::Point.new(0, 100)), + dc.from_dip(Wx::Point.new(-59, -81)), + dc.from_dip(Wx::Point.new(95, 31)), + dc.from_dip(Wx::Point.new(-95, 31)), + dc.from_dip(Wx::Point.new(59, -81))], + [dc.from_dip(Wx::Point.new(0, 80)), + dc.from_dip(Wx::Point.new(-47, -64)), + dc.from_dip(Wx::Point.new(76, 24)), + dc.from_dip(Wx::Point.new(-76, 24)), + dc.from_dip(Wx::Point.new(47, -64))]] + + dc.draw_poly_polygon(star2, dc.from_dip(450), dc.from_dip(150)) + end + + def draw_test_brushes(dc) + _WIDTH = dc.from_dip(200) + _HEIGHT = dc.from_dip(80) + + x = dc.from_dip(10) + y = dc.from_dip(10) + o = dc.from_dip(10) + + dc.set_brush(Wx::GREEN_BRUSH) + dc.draw_rectangle(x, y, _WIDTH, _HEIGHT) + dc.draw_text("Solid green", x + o, y + o) + + y += _HEIGHT + dc.set_brush(Wx::Brush.new(Wx::RED, Wx::BRUSHSTYLE_CROSSDIAG_HATCH)) + dc.draw_rectangle(x, y, _WIDTH, _HEIGHT) + dc.draw_text("Diagonally hatched red", x + o, y + o) + + y += _HEIGHT + dc.set_brush(Wx::Brush.new(Wx::BLUE, Wx::BRUSHSTYLE_CROSS_HATCH)) + dc.draw_rectangle(x, y, _WIDTH, _HEIGHT) + dc.draw_text("Cross hatched blue", x + o, y + o) + + y += _HEIGHT + dc.set_brush(Wx::Brush.new(Wx::CYAN, Wx::BRUSHSTYLE_VERTICAL_HATCH)) + dc.draw_rectangle(x, y, _WIDTH, _HEIGHT) + dc.draw_text("Vertically hatched cyan", x + o, y + o) + + y += _HEIGHT + dc.set_brush(Wx::Brush.new(Wx::BLACK, Wx::BRUSHSTYLE_HORIZONTAL_HATCH)) + dc.draw_rectangle(x, y, _WIDTH, _HEIGHT) + dc.draw_text("Horizontally hatched black", x + o, y + o) + + y += _HEIGHT + dc.set_brush(Wx::Brush.new(Wx.get_app.images[:bmpMask])) + dc.draw_rectangle(x, y, _WIDTH, _HEIGHT) + dc.draw_text("Stipple mono", x + o, y + o) + + y += _HEIGHT + dc.set_brush(Wx::Brush.new(Wx.get_app.images[:bmpNoMask])) + dc.draw_rectangle(x, y, _WIDTH, _HEIGHT) + dc.draw_text("Stipple colour", x + o, y + o) + end + + def draw_text(dc) + # set underlined font for testing + dc.set_font(Wx::FontInfo.new(12).family(Wx::FONTFAMILY_MODERN).underlined) + dc.draw_text("This is text", dc.from_dip(110), dc.from_dip(10) ) + dc.draw_rotated_text("That is text", dc.from_dip(20), dc.from_dip(10), -45) + + # use wxSWISS_FONT and not wxNORMAL_FONT as the latter can't be rotated + # under MSW (it is not TrueType) + dc.set_font(Wx::SWISS_FONT) + + dc.set_background_mode(Wx::BRUSHSTYLE_TRANSPARENT) + + (-180..180).step(30) do |n| + text = " #{n} rotated text" + dc.draw_rotated_text(text , dc.from_dip(400), dc.from_dip(400), n) + end + + dc.set_font(Wx::FontInfo.new(18).family(Wx::FONTFAMILY_SWISS)) + + dc.draw_text("This is Swiss 18pt text.", dc.from_dip(110), dc.from_dip(40)) + + length, height, descent = dc.get_text_extent("This is Swiss 18pt text.") + text = "Dimensions are length #{length}, height #{height}, descent #{descent}" + dc.draw_text(text, dc.from_dip(110), dc.from_dip(80)) + + text = "CharHeight() returns: #{dc.get_char_height}" + dc.draw_text(text, dc.from_dip(110), dc.from_dip(120)) + + dc.draw_rectangle(dc.from_dip(100), dc.from_dip(40), dc.from_dip(4), dc.from_dip(height)) + + # test the logical function effect + y = dc.from_dip(150) + dc.set_logical_function(Wx::INVERT) + # text drawing should ignore logical function + dc.draw_text("There should be a text below", dc.from_dip(110), y) + dc.draw_rectangle(dc.from_dip(110), y, dc.from_dip(100), height) + + y += height + dc.draw_text("Visible text", dc.from_dip(110), y) + dc.draw_rectangle(dc.from_dip(110), y, dc.from_dip(100), height) + dc.draw_text("Visible text", dc.from_dip(110), y) + dc.draw_rectangle(dc.from_dip(110), y, dc.from_dip(100), height) + dc.set_logical_function(Wx::COPY) + + y += height + dc.draw_rectangle(dc.from_dip(110), y, dc.from_dip(100), height) + dc.draw_text("Another visible text", dc.from_dip(110), y) + + y += height + dc.draw_text("And\nmore\ntext on\nmultiple\nlines", dc.from_dip(110), y) + y += 5*height + + dc.set_text_foreground(Wx::BLUE) + dc.draw_rotated_text("Rotated text\ncan have\nmultiple lines\nas well", dc.from_dip(110), y, 15) + + y += 7*height + dc.set_font(Wx::FontInfo.new(12).family(Wx::FONTFAMILY_TELETYPE)) + dc.set_text_foreground(Wx::Colour.new(150, 75, 0)) + dc.draw_text("And some text with tab characters:\n123456789012345678901234567890\n\taa\tbbb\tcccc", dc.from_dip(10), y) + end + + RASTER_OPERATIONS = [ + ["Wx::AND", Wx::AND ], + ["Wx::AND_INVERT", Wx::AND_INVERT ], + ["Wx::AND_REVERSE", Wx::AND_REVERSE ], + ["Wx::CLEAR", Wx::CLEAR ], + ["Wx::COPY", Wx::COPY ], + ["Wx::EQUIV", Wx::EQUIV ], + ["Wx::INVERT", Wx::INVERT ], + ["Wx::NAND", Wx::NAND ], + ["Wx::NO_OP", Wx::NO_OP ], + ["Wx::OR", Wx::OR ], + ["Wx::OR_INVERT", Wx::OR_INVERT ], + ["Wx::OR_REVERSE", Wx::OR_REVERSE ], + ["Wx::SET", Wx::SET ], + ["Wx::SRC_INVERT", Wx::SRC_INVERT ], + ["Wx::XOR", Wx::XOR ], + ] + + def draw_images(dc, mode) + dc.draw_text("original image", 0, 0) + dc.draw_bitmap(Wx.get_app.images[:bmpNoMask], 0, dc.from_dip(20), false) + dc.draw_text("with colour mask", 0, dc.from_dip(100)) + dc.draw_bitmap(Wx.get_app.images[:bmpWithColMask], 0, dc.from_dip(120), true) + dc.draw_text("the mask image", 0, dc.from_dip(200)) + dc.draw_bitmap(Wx.get_app.images[:bmpMask], 0, dc.from_dip(220), false) + dc.draw_text("masked image", 0, dc.from_dip(300)) + dc.draw_bitmap(Wx.get_app.images[:bmpWithMask], 0, dc.from_dip(320), true) + + cx = Wx.get_app.images[:bmpWithColMask].width + cy = Wx.get_app.images[:bmpWithColMask].height + + Wx::MemoryDC.draw_on do |memDC| + RASTER_OPERATIONS.each_with_index do |pair, n| + x = dc.from_dip(120) + dc.from_dip(150)*(n%4) + y = dc.from_dip(20) + dc.from_dip(100)*(n/4) + + name, rop = pair + + dc.draw_text(name, x, y - dc.from_dip(20)) + memDC.select_object(Wx.get_app.images[:bmpWithColMask]) + if mode == DrawMode::Draw_Stretch + dc.stretch_blit(x, y, cx, cy, memDC, 0, 0, cx/2, cy/2, + rop, true) + else + dc.blit(x, y, cx, cy, memDC, 0, 0, rop, true) + end + end + end + end + + def draw_with_logical_ops(dc) + w = dc.from_dip(60) + h = dc.from_dip(60) + + # reuse the text colour here + dc.set_pen(Wx::Pen.new(@owner.colourForeground)) + dc.set_brush(Wx::TRANSPARENT_BRUSH) + + RASTER_OPERATIONS.each_with_index do |pair, n| + x = dc.from_dip(20) + dc.from_dip(150)*(n%4) + y = dc.from_dip(20) + dc.from_dip(100)*(n/4) + + name, rop = pair + + dc.draw_text(name, x, y - dc.from_dip(20)) + dc.set_logical_function(rop) + dc.draw_rectangle(x, y, w, h) + dc.draw_line(x, y, x + w, y + h) + dc.draw_line(x + w, y, x, y + h) + end + + # now some filled rectangles + dc.set_brush(Wx::Brush.new(@owner.colourForeground)) + + RASTER_OPERATIONS.each_with_index do |pair, n| + x = dc.from_dip(20) + dc.from_dip(150)*(n%4) + y = dc.from_dip(500) + dc.from_dip(100)*(n/4) + + name, rop = pair + + dc.draw_text(name, x, y - dc.from_dip(20)) + dc.set_logical_function(rop) + dc.draw_rectangle(x, y, w, h) + end + end + + def draw_alpha(dc) + if DRAWING_DC_SUPPORTS_ALPHA || Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + margin = dc.from_dip(20) + width = dc.from_dip(180) + radius = dc.from_dip(30) + + dc.set_pen(Wx::Pen.new(Wx::Colour.new(128, 0, 0 ), 12)) + dc.set_brush(Wx::RED_BRUSH) + + r = Wx::Rect.new(margin, margin + width * 2 / 3, width, width) + + dc.draw_rounded_rectangle(r.x, r.y, r.width, r.width, radius) + + dc.set_pen(Wx::Pen.new(Wx::Colour.new( 0, 0, 128 ), 12)) + dc.set_brush(Wx::Brush.new(Wx::Colour.new(0, 0, 255, 192))) + + r.offset!(width * 4 / 5, -width * 2 / 3) + + dc.draw_rounded_rectangle(r.x, r.y, r.width, r.width, radius) + + dc.set_pen(Wx::Pen.new(Wx::Colour.new( 128, 128, 0 ), 12)) + dc.set_brush(Wx::Brush.new(Wx::Colour.new( 192, 192, 0, 192))) + + r.offset!(width * 4 / 5, width / 2) + + dc.draw_rounded_rectangle(r.x, r.y, r.width, r.width, radius) + + dc.set_pen(Wx::TRANSPARENT_PEN) + dc.set_brush(Wx::Brush.new(Wx::Colour.new(255,255,128,128) )) + dc.draw_rounded_rectangle( 0 , margin + width / 2 , width * 3 , dc.from_dip(100) , radius) + + dc.set_text_background(Wx::Colour.new(160, 192, 160, 160)) + dc.set_text_foreground(Wx::Colour.new(255, 128, 128, 128)) + dc.set_font(Wx::FontInfo.new(40).family(Wx::FONTFAMILY_SWISS).italic) + dc.draw_text("Hello!", dc.from_dip(120), dc.from_dip(80)) + end + end + + def draw_graphics(gc) + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + _BASE = gc.from_dip(80).to_f + _BASE2 = _BASE / 2 + _BASE4 = _BASE / 4 + + font = Wx::SystemSettings.get_font(Wx::SYS_DEFAULT_GUI_FONT) + gc.set_font(font,Wx::BLACK) + + # make a path that contains a circle and some lines, centered at 0,0 + path = gc.create_path + path.add_circle( 0, 0, _BASE2 ) + path.move_to_point(0, -_BASE2) + path.add_line_to_point(0, _BASE2) + path.move_to_point(-_BASE2, 0) + path.add_line_to_point(_BASE2, 0) + path.close_subpath + path.add_rectangle(-_BASE4, -_BASE4/2, _BASE2, _BASE4) + + # Now use that path to demonstrate various capabilities of the graphics context + gc.push_state # save current translation/scale/other state + gc.translate(gc.from_dip(60), gc.from_dip(75)) # reposition the context origin + + gc.set_pen(Wx::Pen.new("navy")) + gc.set_brush(Wx::Brush.new(:pink)) + + 3.times do |i| + case i + when 0 + label = "StrokePath" + when 1 + label = "FillPath" + when 2 + label = "DrawPath" + end + w, h, _, _ = gc.get_text_extent(label) + gc.draw_text(label, -w/2, -_BASE2 - h - gc.from_dip(4)) + case i + when 0 + gc.stroke_path(path) + when 1 + gc.fill_path(path) + when 2 + gc.draw_path(path) + end + gc.translate(2*_BASE, 0) + end + + gc.pop_state # restore saved state + gc.push_state # save it again + gc.translate(gc.from_dip(60), gc.from_dip(200)) # offset to the lower part of the window + + gc.draw_text("Scale", 0, -_BASE2) + gc.translate(0, gc.from_dip(20)) + + gc.set_brush(Wx::Brush.new(Wx::Colour.new(178, 34, 34, 128)))# 128 == half transparent + 8.times do + gc.scale(1.08, 1.08) # increase scale by 8% + gc.translate(gc.from_dip(5), gc.from_dip(5)) + gc.draw_path(path) + end + + gc.pop_state # restore saved state + gc.push_state # save it again + gc.translate(gc.from_dip(400), gc.from_dip(200)) + + gc.draw_text("Rotate", 0, -_BASE2) + + # Move the origin over to the next location + gc.translate(0, gc.from_dip(75)) + + # draw our path again, rotating it about the central point, + # and changing colors as we go + (0...360).step(30) do |angle| + gc.push_state # save this new current state so we can + # pop back to it at the end of the loop + val = Wx::Image::HSVValue.new(angle / 360.0, 1.0, 1.0).to_rgb + gc.set_brush(Wx::Brush.new(Wx::Colour.new(val.red, val.green, val.blue, 64))) + gc.set_pen(Wx::Pen.new(Wx::Colour.new(val.red, val.green, val.blue, 128))) + + # use translate to artfully reposition each drawn path + gc.translate(1.5 * _BASE2 * Math.cos(Wx.deg_to_rad(angle)), + 1.5 * _BASE2 * Math.sin(Wx.deg_to_rad(angle))) + + # use Rotate to rotate the path + gc.rotate(Wx.deg_to_rad(angle)) + + # now draw it + gc.draw_path(path) + gc.pop_state + end + gc.pop_state + + gc.push_state + gc.translate(gc.from_dip(60), gc.from_dip(400)) + label_text = 'Scaled smiley inside a square' + gc.draw_text(label_text, 0, 0) + # Center a bitmap horizontally + textWidth, _, _, _ = gc.get_text_extent(label_text) + rectSize = gc.from_dip(100) + x0 = (textWidth - rectSize) / 2 + gc.draw_rectangle(x0, _BASE2, rectSize, rectSize) + gc.draw_bitmap(@smile_bmp, x0, _BASE2, rectSize, rectSize) + gc.pop_state + + # Draw graphics bitmap and its sub-bitmap + gc.push_state + gc.translate(gc.from_dip(300), gc.from_dip(400)) + gc.draw_text('Smiley as a graphics bitmap', 0, 0) + + gbmp1 = gc.create_bitmap(@smile_bmp) + gc.draw_bitmap(gbmp1, 0, _BASE2, gc.from_dip(50), gc.from_dip(50)) + bmpw = @smile_bmp.width + bmph = @smile_bmp.height + gbmp2 = gc.create_sub_bitmap(gbmp1, 0, bmph/5, bmpw/2, bmph/2) + gc.draw_bitmap(gbmp2, gc.from_dip(80), _BASE2, gc.from_dip(50), gc.from_dip(50)*(bmph/2)/(bmpw/2)) + gc.pop_state + end + end + + def draw_regions(dc) + dc.draw_text("You should see a red rect partly covered by a cyan one "+ + "on the left", dc.from_dip(10), dc.from_dip(5)) + dc.draw_text("and 5 smileys from which 4 are partially clipped on the right", + dc.from_dip(10), dc.from_dip(5) + dc.get_char_height) + dc.draw_text("The second copy should be identical but right part of it "+ + "should be offset by 10 pixels.", + dc.from_dip(10), dc.from_dip(5) + 2*dc.get_char_height) + + draw_regions_helper(dc, dc.from_dip(10), true) + draw_regions_helper(dc, dc.from_dip(350), false) + end + + def draw_circles(dc) + x = dc.from_dip(100) + y = dc.from_dip(100) + r = dc.from_dip(20) + + dc.set_pen(Wx::RED_PEN) + dc.set_brush(Wx::GREEN_BRUSH) + + dc.draw_text("Some circles", 0, y) + dc.draw_circle(x, y, r) + dc.draw_circle(x + 2*r, y, r) + dc.draw_circle(x + 4*r, y, r) + + y += 2*r + dc.draw_text("And ellipses", 0, y) + dc.draw_ellipse(x - r, y, 2*r, r) + dc.draw_ellipse(x + r, y, 2*r, r) + dc.draw_ellipse(x + 3*r, y, 2*r, r) + + y += 2*r + dc.draw_text("And arcs", 0, y) + dc.draw_arc(x - r, y, x + r, y, x, y) + dc.draw_arc(x + 4*r, y, x + 2*r, y, x + 3*r, y) + dc.draw_arc(x + 5*r, y, x + 5*r, y, x + 6*r, y) + + y += 2*r + dc.draw_elliptic_arc(x - r, y, 2*r, r, 0, 90) + dc.draw_elliptic_arc(x + r, y, 2*r, r, 90, 180) + dc.draw_elliptic_arc(x + 3*r, y, 2*r, r, 180, 270) + dc.draw_elliptic_arc(x + 5*r, y, 2*r, r, 270, 360) + + # same as above, just transparent brush + + dc.set_pen(Wx::RED_PEN) + dc.set_brush(Wx::TRANSPARENT_BRUSH) + + y += 2*r + dc.draw_text("Some circles", 0, y) + dc.draw_circle(x, y, r) + dc.draw_circle(x + 2*r, y, r) + dc.draw_circle(x + 4*r, y, r) + + y += 2*r + dc.draw_text("And ellipses", 0, y) + dc.draw_ellipse(x - r, y, 2*r, r) + dc.draw_ellipse(x + r, y, 2*r, r) + dc.draw_ellipse(x + 3*r, y, 2*r, r) + + y += 2*r + dc.draw_text("And arcs", 0, y) + dc.draw_arc(x - r, y, x + r, y, x, y) + dc.draw_arc(x + 4*r, y, x + 2*r, y, x + 3*r, y) + dc.draw_arc(x + 5*r, y, x + 5*r, y, x + 6*r, y) + + y += 2*r + dc.draw_elliptic_arc(x - r, y, 2*r, r, 0, 90) + dc.draw_elliptic_arc(x + r, y, 2*r, r, 90, 180) + dc.draw_elliptic_arc(x + 3*r, y, 2*r, r, 180, 270) + dc.draw_elliptic_arc(x + 5*r, y, 2*r, r, 270, 360) + end + + def draw_splines(dc) + if Wx.has_feature?(:USE_SPLINES) + dc.draw_text("Some splines", dc.from_dip(10), dc.from_dip(5)) + + # values are hardcoded rather than randomly generated + # so the output can be compared between native + # implementations on platforms with different random + # generators + + _R = dc.from_dip(300) + center = Wx::Point.new(_R + dc.from_dip(20), _R + dc.from_dip(20)) + angles = [ 0, 10, 33, 77, 13, 145, 90 ] + radii = [ 100 , 59, 85, 33, 90 ] + numPoints = 200 + pts = ::Array.new(numPoints, nil) + # wxPoint pts[numPoints] + + # background spline calculation + radius_pos = 0 + angle_pos = 0 + angle = 0 + numPoints.times do |i| + angle += angles[ angle_pos ] + r = _R * radii[ radius_pos ] / 100 + pts[i] = [center.x + (r * Math.cos((angle * Math::PI)/180)).to_i, + center.y + (r * Math.sin((angle * Math::PI)/180)).to_i] + angle_pos += 1 + angle_pos = 0 if angle_pos >= angles.size + radius_pos += 1 + radius_pos = 0 if radius_pos >= radii.size + end + + # background spline drawing + dc.set_pen(Wx::RED_PEN) + dc.draw_spline(pts) + + # less detailed spline calculation + letters = ::Array.new(4) { ::Array.new(5) } + # w + letters[0][0] = Wx::Point.new( 0,1) # O O + letters[0][1] = Wx::Point.new( 1,3) # * * + letters[0][2] = Wx::Point.new( 2,2) # * O * + letters[0][3] = Wx::Point.new( 3,3) # * * * * + letters[0][4] = Wx::Point.new( 4,1) # O O + # x1 + letters[1][0] = Wx::Point.new( 5,1) # O*O + letters[1][1] = Wx::Point.new( 6,1) # * + letters[1][2] = Wx::Point.new( 7,2) # O + letters[1][3] = Wx::Point.new( 8,3) # * + letters[1][4] = Wx::Point.new( 9,3) # O*O + # x2 + letters[2][0] = Wx::Point.new( 5,3) # O*O + letters[2][1] = Wx::Point.new( 6,3) # * + letters[2][2] = Wx::Point.new( 7,2) # O + letters[2][3] = Wx::Point.new( 8,1) # * + letters[2][4] = Wx::Point.new( 9,1) # O*O + # W + letters[3][0] = Wx::Point.new(10,0) # O O + letters[3][1] = Wx::Point.new(11,3) # * * + letters[3][2] = Wx::Point.new(12,1) # * O * + letters[3][3] = Wx::Point.new(13,3) # * * * * + letters[3][4] = Wx::Point.new(14,0) # O O + + dx = 2 * _R / letters[3][4].x + h = [ (-_R/2), 0, _R/4, _R/2 ] + + letters.each_with_index do |row, m| + row.each_with_index do |pt, n| + pt.x = center.x - _R + (pt.x * dx) + pt.y = center.y + h[pt.y] + end + + dc.set_pen(Wx::Pen.new(Wx::BLUE, 1, Wx::PENSTYLE_DOT)) + dc.draw_lines(letters[m]) + dc.set_pen(Wx::Pen.new(Wx::BLACK, 4)) + dc.draw_spline(letters[m]) + end + + else + dc.draw_text('Splines not supported.', 10, 5) + end + end + + def draw_default(dc) + # Draw circle centered at the origin, then flood fill it with a different + # color. Done with a Wx::MemoryDC because Blit (used by generic + # Wx.do_flood_fill) from a window that is being painted gives unpredictable + # results on WXGTK + img = Wx::Image.new(dc.from_dip(21), dc.from_dip(21), false) + img.clear(1) + bmp = img.to_bitmap + Wx::MemoryDC.draw_on(bmp) do |mdc| + mdc.set_brush(dc.get_brush) + mdc.set_pen(dc.get_pen) + mdc.draw_circle(dc.from_dip(10), dc.from_dip(10), dc.from_dip(10)) + c = Wx::Colour.new + if mdc.get_pixel(dc.from_dip(11), dc.from_dip(11), c) + mdc.set_brush(Wx::Brush.new(Wx::Colour.new(128, 128, 0))) + mdc.flood_fill(dc.from_dip(11), dc.from_dip(11), c, Wx::FLOOD_SURFACE) + end + end + bmp.set_mask(Wx::Mask.new(bmp, Wx::Colour.new(1, 1, 1))) + dc.draw_bitmap(bmp, dc.from_dip(-10), dc.from_dip(-10), true) + + dc.draw_check_mark(dc.from_dip(5), dc.from_dip(80), dc.from_dip(15), dc.from_dip(15)) + dc.draw_check_mark(dc.from_dip(25), dc.from_dip(80), dc.from_dip(30), dc.from_dip(30)) + dc.draw_check_mark(dc.from_dip(60), dc.from_dip(80), dc.from_dip(60), dc.from_dip(60)) + + # this is the test for "blitting bitmap into DC damages selected brush" bug + rectSize = @std_icon.width + dc.from_dip(10) + x = dc.from_dip(100) + dc.set_pen(Wx::TRANSPARENT_PEN) + dc.set_brush(Wx::GREEN_BRUSH) + dc.draw_rectangle(x, dc.from_dip(10), rectSize, rectSize) + dc.draw_bitmap(@std_icon, x + dc.from_dip(5), dc.from_dip(15), true) + x += rectSize + dc.from_dip(10) + dc.draw_rectangle(x, dc.from_dip(10), rectSize, rectSize) + dc.draw_icon(@std_icon, x + dc.from_dip(5), dc.from_dip(15)) + x += rectSize + dc.from_dip(10) + dc.draw_rectangle(x, dc.from_dip(10), rectSize, rectSize) + + # test for "transparent" bitmap drawing (it intersects with the last + # rectangle above) + #dc.set_brush(Wx::TRANSPARENT_BRUSH) + + dc.draw_bitmap(@smile_bmp, x + rectSize - dc.from_dip(20), rectSize - dc.from_dip(10), true) if (@smile_bmp.ok?) + + dc.set_brush(Wx::BLACK_BRUSH) + dc.draw_rectangle(0, dc.from_dip(160), dc.from_dip(1000), dc.from_dip(300)) + + # draw lines + bitmap = Wx::Bitmap.new(dc.from_dip([20,70])) + Wx::MemoryDC.draw_on(bitmap) do |memdc| + memdc.set_brush(Wx::BLACK_BRUSH) + memdc.set_pen(Wx::WHITE_PEN) + memdc.draw_rectangle(0, 0, dc.from_dip(20), dc.from_dip(70)) + memdc.draw_line( dc.from_dip(10), 0, dc.from_dip(10), dc.from_dip(70) ) + + # to the right + pen = Wx::RED_PEN + memdc.set_pen(pen) + memdc.draw_line(dc.from_dip(10), dc.from_dip(5), dc.from_dip(10), dc.from_dip(5) ) + memdc.draw_line(dc.from_dip(10), dc.from_dip(10), dc.from_dip(11), dc.from_dip(10)) + memdc.draw_line(dc.from_dip(10), dc.from_dip(15), dc.from_dip(12), dc.from_dip(15)) + memdc.draw_line(dc.from_dip(10), dc.from_dip(20), dc.from_dip(13), dc.from_dip(20)) + + # memdc.set_pen(Wx::RED_PEN) + # memdc.draw_line( dc.from_dip(12),dc.from_dip( 5),dc.from_dip(12),dc.from_dip( 5) ) + # memdc.draw_line( dc.from_dip(12),dc.from_dip(10),dc.from_dip(13),dc.from_dip(10) ) + # memdc.draw_line( dc.from_dip(12),dc.from_dip(15),dc.from_dip(14),dc.from_dip(15) ) + # memdc.draw_line( dc.from_dip(12),dc.from_dip(20),dc.from_dip(15),dc.from_dip(20) ) + + # same to the left + memdc.draw_line(dc.from_dip(10), dc.from_dip(25), dc.from_dip(10), dc.from_dip(25)) + memdc.draw_line(dc.from_dip(10), dc.from_dip(30), dc.from_dip(9), dc.from_dip(30)) + memdc.draw_line(dc.from_dip(10), dc.from_dip(35), dc.from_dip(8), dc.from_dip(35)) + memdc.draw_line(dc.from_dip(10), dc.from_dip(40), dc.from_dip(7), dc.from_dip(40)) + + # XOR draw lines + dc.set_pen(Wx::WHITE_PEN) + memdc.set_logical_function(Wx::INVERT) + memdc.set_pen(Wx::WHITE_PEN) + memdc.draw_line(dc.from_dip(10), dc.from_dip(50), dc.from_dip(10), dc.from_dip(50)) + memdc.draw_line(dc.from_dip(10), dc.from_dip(55), dc.from_dip(11), dc.from_dip(55)) + memdc.draw_line(dc.from_dip(10), dc.from_dip(60), dc.from_dip(12), dc.from_dip(60)) + memdc.draw_line(dc.from_dip(10), dc.from_dip(65), dc.from_dip(13), dc.from_dip(65)) + + memdc.draw_line(dc.from_dip(12), dc.from_dip(50), dc.from_dip(12), dc.from_dip(50)) + memdc.draw_line(dc.from_dip(12), dc.from_dip(55), dc.from_dip(13), dc.from_dip(55)) + memdc.draw_line(dc.from_dip(12), dc.from_dip(60), dc.from_dip(14), dc.from_dip(60)) + memdc.draw_line(dc.from_dip(12), dc.from_dip(65), dc.from_dip(15), dc.from_dip(65)) + end + dc.draw_bitmap(bitmap, dc.from_dip(10), dc.from_dip(170)) + image = bitmap.convert_to_image + image.rescale(dc.from_dip(60), dc.from_dip(210)) + bitmap = image.to_bitmap + dc.draw_bitmap(bitmap, dc.from_dip(50), dc.from_dip(170)) + + # test the rectangle outline drawing - there should be one pixel between + # the rect and the lines + dc.set_pen(Wx::WHITE_PEN) + dc.set_brush(Wx::TRANSPARENT_BRUSH) + dc.draw_rectangle(dc.from_dip(150), dc.from_dip(170), dc.from_dip(49), dc.from_dip(29)) + dc.draw_rectangle(dc.from_dip(200), dc.from_dip(170), dc.from_dip(49), dc.from_dip(29)) + dc.set_pen(Wx::WHITE_PEN) + dc.draw_line(dc.from_dip(250), dc.from_dip(210), dc.from_dip(250), dc.from_dip(170)) + dc.draw_line(dc.from_dip(260), dc.from_dip(200), dc.from_dip(150), dc.from_dip(200)) + + # test the rectangle filled drawing - there should be one pixel between + # the rect and the lines + dc.set_pen(Wx::TRANSPARENT_PEN) + dc.set_brush(Wx::WHITE_BRUSH) + dc.draw_rectangle(dc.from_dip(300), dc.from_dip(170), dc.from_dip(49), dc.from_dip(29)) + dc.draw_rectangle(dc.from_dip(350), dc.from_dip(170), dc.from_dip(49), dc.from_dip(29)) + dc.set_pen(Wx::WHITE_PEN) + dc.draw_line(dc.from_dip(400), dc.from_dip(170), dc.from_dip(400), dc.from_dip(210)) + dc.draw_line(dc.from_dip(300), dc.from_dip(200), dc.from_dip(410), dc.from_dip(200)) + + # a few more tests of this kind + dc.set_pen(Wx::RED_PEN) + dc.set_brush(Wx::WHITE_BRUSH) + dc.draw_rectangle(dc.from_dip(300), dc.from_dip(220), dc.from_dip(1), dc.from_dip(1)) + dc.draw_rectangle(dc.from_dip(310), dc.from_dip(220), dc.from_dip(2), dc.from_dip(2)) + dc.draw_rectangle(dc.from_dip(320), dc.from_dip(220), dc.from_dip(3), dc.from_dip(3)) + dc.draw_rectangle(dc.from_dip(330), dc.from_dip(220), dc.from_dip(4), dc.from_dip(4)) + + dc.set_pen(Wx::TRANSPARENT_PEN) + dc.set_brush(Wx::WHITE_BRUSH) + dc.draw_rectangle(dc.from_dip(300), dc.from_dip(230), dc.from_dip(1), dc.from_dip(1)) + dc.draw_rectangle(dc.from_dip(310), dc.from_dip(230), dc.from_dip(2), dc.from_dip(2)) + dc.draw_rectangle(dc.from_dip(320), dc.from_dip(230), dc.from_dip(3), dc.from_dip(3)) + dc.draw_rectangle(dc.from_dip(330), dc.from_dip(230), dc.from_dip(4), dc.from_dip(4)) + + # and now for filled rect with outline + dc.set_pen(Wx::RED_PEN) + dc.set_brush(Wx::WHITE_BRUSH) + dc.draw_rectangle(dc.from_dip(500), dc.from_dip(170), dc.from_dip(49), dc.from_dip(29)) + dc.draw_rectangle(dc.from_dip(550), dc.from_dip(170), dc.from_dip(49), dc.from_dip(29)) + dc.set_pen(Wx::WHITE_PEN) + dc.draw_line(dc.from_dip(600), dc.from_dip(170), dc.from_dip(600), dc.from_dip(210)) + dc.draw_line(dc.from_dip(500), dc.from_dip(200), dc.from_dip(610), dc.from_dip(200)) + + # test the rectangle outline drawing - there should be one pixel between + # the rect and the lines + dc.set_pen(Wx::WHITE_PEN) + dc.set_brush( Wx::TRANSPARENT_BRUSH ) + dc.draw_rounded_rectangle(dc.from_dip(150), dc.from_dip(270), dc.from_dip(49), dc.from_dip(29), dc.from_dip(6)) + dc.draw_rounded_rectangle(dc.from_dip(200), dc.from_dip(270), dc.from_dip(49), dc.from_dip(29), dc.from_dip(6)) + dc.set_pen(Wx::WHITE_PEN) + dc.draw_line(dc.from_dip(250), dc.from_dip(270), dc.from_dip(250), dc.from_dip(310)) + dc.draw_line(dc.from_dip(150), dc.from_dip(300), dc.from_dip(260), dc.from_dip(300)) + + # test the rectangle filled drawing - there should be one pixel between + # the rect and the lines + dc.set_pen(Wx::TRANSPARENT_PEN) + dc.set_brush( Wx::WHITE_BRUSH ) + dc.draw_rounded_rectangle(dc.from_dip(300), dc.from_dip(270), dc.from_dip(49), dc.from_dip(29), dc.from_dip(6)) + dc.draw_rounded_rectangle(dc.from_dip(350), dc.from_dip(270), dc.from_dip(49), dc.from_dip(29), dc.from_dip(6)) + dc.set_pen(Wx::WHITE_PEN) + dc.draw_line(dc.from_dip(400), dc.from_dip(270), dc.from_dip(400), dc.from_dip(310)) + dc.draw_line(dc.from_dip(300), dc.from_dip(300), dc.from_dip(410), dc.from_dip(300)) + + # Added by JACS to demonstrate bizarre behaviour. + # With a size of 70, we get a missing red RHS, + # and the height is too small, so we get yellow + # showing. With a size of 40, it draws as expected: + # it just shows a white rectangle with red outline. + totalWidth = dc.from_dip(70) + totalHeight = dc.from_dip(70) + bitmap2 = Wx::Bitmap.new(totalWidth, totalHeight) + + Wx::MemoryDC.draw_on(bitmap2) do |memdc2| + memdc2.set_background(Wx::YELLOW_BRUSH) + memdc2.clear + + # Now draw a white rectangle with red outline. It should + # entirely eclipse the yellow background. + memdc2.set_pen(Wx::RED_PEN) + memdc2.set_brush(Wx::WHITE_BRUSH) + + memdc2.draw_rectangle(0, 0, totalWidth, totalHeight) + end + + dc.draw_bitmap(bitmap2, dc.from_dip(500), dc.from_dip(270)) + + # Repeat, but draw directly on dc + # Draw a yellow rectangle filling the bitmap + + x = dc.from_dip(600) + y = dc.from_dip(270) + dc.set_pen(Wx::YELLOW_PEN) + dc.set_brush(Wx::YELLOW_BRUSH) + dc.draw_rectangle(x, y, totalWidth, totalHeight) + + # Now draw a white rectangle with red outline. It should + # entirely eclipse the yellow background. + dc.set_pen(Wx::RED_PEN) + dc.set_brush(Wx::WHITE_BRUSH) + + dc.draw_rectangle(x, y, totalWidth, totalHeight) + end + + def draw_gradients(dc) + text_height = dc.get_char_height + + # LHS: linear + r = Wx::Rect.new(dc.from_dip(10), dc.from_dip(10), dc.from_dip(50), dc.from_dip(50)) + dc.draw_text("Wx::RIGHT", r.x, r.y) + r.offset!(0, text_height) + dc.gradient_fill_linear(r, Wx::WHITE, Wx::BLUE, Wx::RIGHT) + + r.offset!(0, r.height + dc.from_dip(10)) + dc.draw_text("Wx::LEFT", r.x, r.y) + r.offset!(0, text_height) + dc.gradient_fill_linear(r, Wx::WHITE, Wx::BLUE, Wx::LEFT) + + r.offset!(0, r.height + dc.from_dip(10)) + dc.draw_text("Wx::DOWN", r.x, r.y) + r.offset!(0, text_height) + dc.gradient_fill_linear(r, Wx::WHITE, Wx::BLUE, Wx::DOWN) + + r.offset!(0, r.height + dc.from_dip(10)) + dc.draw_text("Wx::UP", r.x, r.y) + r.offset!(0, text_height) + dc.gradient_fill_linear(r, Wx::WHITE, Wx::BLUE, Wx::UP) + + gfr = Wx::Rect.new.assign(r) + + # RHS: concentric + r = Wx::Rect.new(dc.from_dip(200), dc.from_dip(10), dc.from_dip(50), dc.from_dip(50)) + dc.draw_text("Blue inside", r.x, r.y) + r.offset!(0, text_height) + dc.gradient_fill_concentric(r, Wx::BLUE, Wx::WHITE) + + r.offset!(0, r.height + dc.from_dip(10)) + dc.draw_text("White inside", r.x, r.y) + r.offset!(0, text_height) + dc.gradient_fill_concentric(r, Wx::WHITE, Wx::BLUE) + + r.offset!(0, r.height + dc.from_dip(10)) + dc.draw_text("Blue in top left corner", r.x, r.y) + r.offset!(0, text_height) + dc.gradient_fill_concentric(r, Wx::BLUE, Wx::WHITE, [0, 0]) + + r.offset!(0, r.height + dc.from_dip(10)) + dc.draw_text("Blue in bottom right corner", r.x, r.y) + r.offset!(0, text_height) + dc.gradient_fill_concentric(r, Wx::BLUE, Wx::WHITE, [r.width, r.height]) + + # check that the area filled by the gradient is exactly the interior of + # the rectangle + r.x = dc.from_dip(350) + r.y = dc.from_dip(30) + dc.draw_text("The interior should be filled but", r.x, r.y) + r.y += text_height + dc.draw_text(" the red border should remain visible:", r.x, r.y) + r.y += text_height + + r.width = + r.height = dc.from_dip(50) + r2 = Wx::Rect.new.assign(r) + r2.x += dc.from_dip(60) + r3 = Wx::Rect.new.assign(r) + r3.y += dc.from_dip(60) + r4 = Wx::Rect.new.assign(r2) + r4.y += dc.from_dip(60) + dc.set_pen(Wx::RED_PEN) + dc.draw_rectangle(r) + r.deflate!(1) + dc.gradient_fill_linear(r, Wx::GREEN, Wx::BLACK, Wx::NORTH) + dc.draw_rectangle(r2) + r2.deflate!(1) + dc.gradient_fill_linear(r2, Wx::BLACK, Wx::GREEN, Wx::SOUTH) + dc.draw_rectangle(r3) + r3.deflate!(1) + dc.gradient_fill_linear(r3, Wx::GREEN, Wx::BLACK, Wx::EAST) + dc.draw_rectangle(r4) + r4.deflate!(1) + dc.gradient_fill_linear(r4, Wx::BLACK, Wx::GREEN, Wx::WEST) + + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + if @renderer + gc = dc.get_graphics_context + # double boxX, boxY, boxWidth, boxHeight + + gfr.offset!(0, gfr.height + gc.from_dip(10)) + dc.draw_text("Linear Gradient with Stops", gfr.x, gfr.y) + gfr.offset!(0, text_height) + + stops = Wx::GraphicsGradientStops.new(Wx::RED, Wx::BLUE) + stops.add(Wx::Colour.new(255,255,0), 0.33) + stops.add(Wx::GREEN, 0.67) + + gc.set_brush(gc.create_linear_gradient_brush(gfr.x, gfr.y, + gfr.x + gfr.width, gfr.y + gfr.height, + stops)) + pth = gc.create_path + pth.move_to_point(gfr.x,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y+gfr.height) + pth.add_line_to_point(gfr.x,gfr.y+gfr.height) + pth.close_subpath + gc.fill_path(pth) + boxX, boxY, boxWidth, boxHeight = pth.get_box + dc.calc_bounding_box(boxX.round, boxY.round) + dc.calc_bounding_box((boxX+boxWidth).round, (boxY+boxHeight).round) + + simpleStops = Wx::GraphicsGradientStops.new(Wx::RED, Wx::BLUE) + + gfr.offset!(0, gfr.height + gc.from_dip(10)) + dc.draw_text("Radial Gradient from Red to Blue without intermediary Stops", + gfr.x, gfr.y) + gfr.offset!(0, text_height) + + gc.set_brush(gc.create_radial_gradient_brush(gfr.x + gfr.width / 2, + gfr.y + gfr.height / 2, + gfr.x + gfr.width / 2, + gfr.y + gfr.height / 2, + gfr.width / 2, + simpleStops)) + + pth = gc.create_path + pth.move_to_point(gfr.x,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y+gfr.height) + pth.add_line_to_point(gfr.x,gfr.y+gfr.height) + pth.close_subpath + gc.fill_path(pth) + boxX, boxY, boxWidth, boxHeight = pth.get_box + dc.calc_bounding_box(boxX.round, boxY.round) + dc.calc_bounding_box((boxX+boxWidth).round, (boxY+boxHeight).round) + + gfr.offset!(0, gfr.height + gc.from_dip(10)) + dc.draw_text("Radial Gradient from Red to Blue with Yellow and Green Stops", + gfr.x, gfr.y) + gfr.offset!(0, text_height) + + gc.set_brush(gc.create_radial_gradient_brush(gfr.x + gfr.width / 2, + gfr.y + gfr.height / 2, + gfr.x + gfr.width / 2, + gfr.y + gfr.height / 2, + gfr.width / 2, + stops)) + pth = gc.create_path + pth.move_to_point(gfr.x,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y+gfr.height) + pth.add_line_to_point(gfr.x,gfr.y+gfr.height) + pth.close_subpath + gc.fill_path(pth) + boxX, boxY, boxWidth, boxHeight = pth.get_box + dc.calc_bounding_box(boxX.round, boxY.round) + dc.calc_bounding_box((boxX+boxWidth).round, (boxY+boxHeight).round) + + gfr.offset!(0, gfr.height + gc.from_dip(10)) + dc.draw_text("Linear Gradient with Stops and Gaps", gfr.x, gfr.y) + gfr.offset!(0, text_height) + + stops = Wx::GraphicsGradientStops.new(Wx::RED, Wx::BLUE) + stops.add(Wx::Colour.new(255,255,0), 0.33) + stops.add(Wx::TRANSPARENT_COLOUR, 0.33) + stops.add(Wx::TRANSPARENT_COLOUR, 0.67) + stops.add(Wx::GREEN, 0.67) + + gc.set_brush(gc.create_linear_gradient_brush(gfr.x, gfr.y + gfr.height, + gfr.x + gfr.width, gfr.y, + stops)) + pth = gc.create_path + pth.move_to_point(gfr.x,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y+gfr.height) + pth.add_line_to_point(gfr.x,gfr.y+gfr.height) + pth.close_subpath + gc.fill_path(pth) + boxX, boxY, boxWidth, boxHeight = pth.get_box + dc.calc_bounding_box(boxX.round, boxY.round) + dc.calc_bounding_box((boxX+boxWidth).round, (boxY+boxHeight).round) + + gfr.offset!(0, gfr.height + gc.from_dip(10)) + dc.draw_text("Radial Gradient with Stops and Gaps", gfr.x, gfr.y) + gfr.offset!(0, text_height) + + gc.set_brush(gc.create_radial_gradient_brush(gfr.x + gfr.width / 2, + gfr.y + gfr.height / 2, + gfr.x + gfr.width / 2, + gfr.y + gfr.height / 2, + gfr.width / 2, + stops)) + pth = gc.create_path + pth.move_to_point(gfr.x,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y+gfr.height) + pth.add_line_to_point(gfr.x,gfr.y+gfr.height) + pth.close_subpath + gc.fill_path(pth) + boxX, boxY, boxWidth, boxHeight = pth.get_box + dc.calc_bounding_box(boxX.round, boxY.round) + dc.calc_bounding_box((boxX+boxWidth).round, (boxY+boxHeight).round) + + gfr.offset!(0, gfr.height + gc.from_dip(10)) + dc.draw_text("Gradients with Stops and Transparency", gfr.x, gfr.y) + gfr.offset!(0, text_height) + + stops = Wx::GraphicsGradientStops.new(Wx::RED, Wx::TRANSPARENT_COLOUR) + stops.add(Wx::RED, 0.33) + stops.add(Wx::TRANSPARENT_COLOUR, 0.33) + stops.add(Wx::TRANSPARENT_COLOUR, 0.67) + stops.add(Wx::BLUE, 0.67) + stops.add(Wx::BLUE, 1.0) + + pth = gc.create_path + pth.move_to_point(gfr.x,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y) + pth.add_line_to_point(gfr.x + gfr.width,gfr.y+gfr.height) + pth.add_line_to_point(gfr.x,gfr.y+gfr.height) + pth.close_subpath + + gc.set_brush(gc.create_radial_gradient_brush(gfr.x + gfr.width / 2, + gfr.y + gfr.height / 2, + gfr.x + gfr.width / 2, + gfr.y + gfr.height / 2, + gfr.width / 2, + stops)) + gc.fill_path(pth) + + stops = Wx::GraphicsGradientStops.new(Wx::Colour.new(255,0,0, 128), Wx::Colour.new(0,0,255, 128)) + stops.add(Wx::Colour.new(255,255,0,128), 0.33) + stops.add(Wx::Colour.new(0,255,0,128), 0.67) + + gc.set_brush(gc.create_linear_gradient_brush(gfr.x, gfr.y, + gfr.x + gfr.width, gfr.y, + stops)) + gc.fill_path(pth) + boxX, boxY, boxWidth, boxHeight = pth.get_box + dc.calc_bounding_box(boxX.round, boxY.round) + dc.calc_bounding_box((boxX+boxWidth).round, (boxY+boxHeight).round) + + gfr.offset!(0, gfr.height + gc.from_dip(10)) + dc.draw_text("Stroked path with a gradient pen", gfr.x, gfr.y) + gfr.offset!(0, text_height) + + pth = gc.create_path + pth.move_to_point(gfr.x + gfr.width/2, gfr.y) + pth.add_line_to_point(gfr.x + gfr.width, gfr.y + gfr.height/2) + pth.add_line_to_point(gfr.x + gfr.width/2, gfr.y + gfr.height) + pth.add_line_to_point(gfr.x, gfr.y + gfr.height/2) + pth.close_subpath + + stops = Wx::GraphicsGradientStops.new(Wx::RED, Wx::BLUE) + stops.add(Wx::Colour.new(255,255,0), 0.33) + stops.add(Wx::GREEN, 0.67) + + pen = gc.create_pen( + Wx::GraphicsPenInfo.new(Wx::Colour.new(0,0,0)).width(6).join(Wx::JOIN_BEVEL).linear_gradient( + gfr.x + gfr.width/2, gfr.y, + gfr.x + gfr.width/2, gfr.y + gfr.height, + stops)) + gc.set_pen(pen) + gc.stroke_path(pth) + end + end # USE_GRAPHICS_CONTEXT + end + + SYSTEM_COLOURS = { + Wx::SYS_COLOUR_3DDKSHADOW => "Wx::SYS_COLOUR_3DDKSHADOW" , + Wx::SYS_COLOUR_3DLIGHT => "Wx::SYS_COLOUR_3DLIGHT" , + Wx::SYS_COLOUR_ACTIVEBORDER => "Wx::SYS_COLOUR_ACTIVEBORDER" , + Wx::SYS_COLOUR_ACTIVECAPTION => "Wx::SYS_COLOUR_ACTIVECAPTION" , + Wx::SYS_COLOUR_APPWORKSPACE => "Wx::SYS_COLOUR_APPWORKSPACE" , + Wx::SYS_COLOUR_BTNFACE => "Wx::SYS_COLOUR_BTNFACE" , + Wx::SYS_COLOUR_BTNHIGHLIGHT => "Wx::SYS_COLOUR_BTNHIGHLIGHT" , + Wx::SYS_COLOUR_BTNSHADOW => "Wx::SYS_COLOUR_BTNSHADOW" , + Wx::SYS_COLOUR_BTNTEXT => "Wx::SYS_COLOUR_BTNTEXT" , + Wx::SYS_COLOUR_CAPTIONTEXT => "Wx::SYS_COLOUR_CAPTIONTEXT" , + Wx::SYS_COLOUR_DESKTOP => "Wx::SYS_COLOUR_DESKTOP" , + Wx::SYS_COLOUR_GRADIENTACTIVECAPTION => "Wx::SYS_COLOUR_GRADIENTACTIVECAPTION" , + Wx::SYS_COLOUR_GRADIENTINACTIVECAPTION => "Wx::SYS_COLOUR_GRADIENTINACTIVECAPTION" , + Wx::SYS_COLOUR_GRAYTEXT => "Wx::SYS_COLOUR_GRAYTEXT" , + Wx::SYS_COLOUR_HIGHLIGHTTEXT => "Wx::SYS_COLOUR_HIGHLIGHTTEXT" , + Wx::SYS_COLOUR_HIGHLIGHT => "Wx::SYS_COLOUR_HIGHLIGHT" , + Wx::SYS_COLOUR_HOTLIGHT => "Wx::SYS_COLOUR_HOTLIGHT" , + Wx::SYS_COLOUR_INACTIVEBORDER => "Wx::SYS_COLOUR_INACTIVEBORDER" , + Wx::SYS_COLOUR_INACTIVECAPTIONTEXT => "Wx::SYS_COLOUR_INACTIVECAPTIONTEXT" , + Wx::SYS_COLOUR_INACTIVECAPTION => "Wx::SYS_COLOUR_INACTIVECAPTION" , + Wx::SYS_COLOUR_INFOBK => "Wx::SYS_COLOUR_INFOBK" , + Wx::SYS_COLOUR_INFOTEXT => "Wx::SYS_COLOUR_INFOTEXT" , + Wx::SYS_COLOUR_LISTBOXHIGHLIGHTTEXT => "Wx::SYS_COLOUR_LISTBOXHIGHLIGHTTEXT" , + Wx::SYS_COLOUR_LISTBOXTEXT => "Wx::SYS_COLOUR_LISTBOXTEXT" , + Wx::SYS_COLOUR_LISTBOX => "Wx::SYS_COLOUR_LISTBOX" , + Wx::SYS_COLOUR_MENUBAR => "Wx::SYS_COLOUR_MENUBAR" , + Wx::SYS_COLOUR_MENUHILIGHT => "Wx::SYS_COLOUR_MENUHILIGHT" , + Wx::SYS_COLOUR_MENUTEXT => "Wx::SYS_COLOUR_MENUTEXT" , + Wx::SYS_COLOUR_MENU => "Wx::SYS_COLOUR_MENU" , + Wx::SYS_COLOUR_SCROLLBAR => "Wx::SYS_COLOUR_SCROLLBAR" , + Wx::SYS_COLOUR_WINDOWFRAME => "Wx::SYS_COLOUR_WINDOWFRAME" , + Wx::SYS_COLOUR_WINDOWTEXT => "Wx::SYS_COLOUR_WINDOWTEXT" , + Wx::SYS_COLOUR_WINDOW => "Wx::SYS_COLOUR_WINDOW" + } + + def draw_system_colours(dc) + mono = Wx::Font.new(Wx::FontInfo.new.family(Wx::FONTFAMILY_TELETYPE)) + textWidth, textHeight, _, _ = dc.with_font(mono) { dc.get_text_extent("#01234567") } + + x = from_dip(10) + r = Wx::Rect.new(textWidth + x, x, dc.from_dip(100), textHeight) + + title = 'System colours' + + appearanceName = Wx::SystemSettings.get_appearance_name + title << " for \"#{appearanceName}\"" unless appearanceName.empty? + + title += " (using dark system theme)" if Wx::SystemSettings.is_appearance_dark + dc.draw_text(title, x, r.y) + r.y += 2*textHeight + dc.draw_text("Window background is #{Wx::SystemSettings.is_appearance_using_dark_background ? 'dark' : 'light'}", + x, r.y) + r.y += 3*textHeight + + dc.set_pen(Wx::TRANSPARENT_PEN) + + SYSTEM_COLOURS.each_pair do |index, name| + c = Wx::Colour.new(Wx::SystemSettings.get_colour(index)) + dc.with_font(mono) { dc.draw_text(c.get_as_string(Wx::C2S_HTML_SYNTAX), x, r.y) } + + dc.set_brush(Wx::Brush.new(c)) + dc.draw_rectangle(r) + + dc.draw_text(name, r.right + x, r.y) + + r.y += textHeight + end + end + + def draw_regions_helper(dc, x, firstTime) + y = dc.from_dip(100) + + dc.destroy_clipping_region + dc.set_brush(Wx::WHITE_BRUSH) + dc.set_pen(Wx::TRANSPARENT_PEN) + dc.draw_rectangle(x, y, dc.from_dip(310), dc.from_dip(310)) + + dc.set_clipping_region(x + dc.from_dip(10), y + dc.from_dip(10), dc.from_dip(100), dc.from_dip(270)) + + dc.set_brush(Wx::RED_BRUSH) + dc.draw_rectangle(x, y, dc.from_dip(310), dc.from_dip(310)) + + dc.set_clipping_region(x + dc.from_dip(10), y + dc.from_dip(10), dc.from_dip(100), dc.from_dip(100)) + + dc.set_brush(Wx::CYAN_BRUSH) + dc.draw_rectangle(x, y, dc.from_dip(310), dc.from_dip(310)) + + dc.destroy_clipping_region + + region = Wx::Region.new(x + dc.from_dip(110), y + dc.from_dip(20), dc.from_dip(100), dc.from_dip(270)) + region.offset(dc.from_dip(10), dc.from_dip(10)) unless firstTime + dc.set_device_clipping_region(region) + + dc.set_brush(Wx::GREY_BRUSH) + dc.draw_rectangle(x, y, dc.from_dip(310), dc.from_dip(310)) + + if @smile_bmp.ok? + dc.draw_bitmap(@smile_bmp, x + dc.from_dip(150), y + dc.from_dip(150), true) + dc.draw_bitmap(@smile_bmp, x + dc.from_dip(130), y + dc.from_dip(10), true) + dc.draw_bitmap(@smile_bmp, x + dc.from_dip(130), y + dc.from_dip(280), true) + dc.draw_bitmap(@smile_bmp, x + dc.from_dip(100), y + dc.from_dip(70), true) + dc.draw_bitmap(@smile_bmp, x + dc.from_dip(200), y + dc.from_dip(70), true) + end + end + + end + + if Wx.has_feature?(:USE_DC_TRANSFORM_MATRIX) + + class TransformDataDialog < Wx::Dialog + + def initialize(parent, dx, dy, scx, scy, rotAngle) + super(parent, Wx::ID_ANY, 'Affine transformation parameters') + @dx = dx + @dy = dy + @scx = scx + @scy = scy + @rotAngle = rotAngle + + sizer = Wx::VBoxSizer.new + + border = Wx::SizerFlags.get_default_border + paramSizer = Wx::FlexGridSizer.new(2, [border, border]) + paramSizer.add(Wx::StaticText.new(self, Wx::ID_ANY, 'Translation X:'), Wx::SizerFlags.new.centre_vertical) + val_dx = Wx::FloatValidator.new(1, Wx::NUM_VAL_NO_TRAILING_ZEROES) + val_dx.on_transfer_from_window { |v| @dx = v } + val_dx.on_transfer_to_window { @dx } + paramSizer.add(Wx::TextCtrl.new(self, Wx::ID_ANY, style: 0, validator: val_dx), Wx::SizerFlags.new.centre_vertical) + paramSizer.add(Wx::StaticText.new(self, Wx::ID_ANY, 'Translation Y:'), Wx::SizerFlags.new.centre_vertical) + val_dy = Wx::FloatValidator.new(1, Wx::NUM_VAL_NO_TRAILING_ZEROES) + val_dy.on_transfer_from_window { |v| @dy = v } + val_dy.on_transfer_to_window { @dy } + paramSizer.add(Wx::TextCtrl.new(self, Wx::ID_ANY, style: 0, validator: val_dy), Wx::SizerFlags.new.centre_vertical) + paramSizer.add(Wx::StaticText.new(self, Wx::ID_ANY, 'Scale X (0.2 - 5):'), Wx::SizerFlags.new.centre_vertical) + val_scx = Wx::FloatValidator.new(1, Wx::NUM_VAL_NO_TRAILING_ZEROES) + val_scx.on_transfer_from_window { |v| @scx = v } + val_scx.on_transfer_to_window { @scx } + paramSizer.add(Wx::TextCtrl.new(self, Wx::ID_ANY, style: 0, validator: val_scx), Wx::SizerFlags.new.centre_vertical) + paramSizer.add(Wx::StaticText.new(self, Wx::ID_ANY, 'Scale Y (0.2 - 5):'), Wx::SizerFlags.new.centre_vertical) + val_scy = Wx::FloatValidator.new(1, Wx::NUM_VAL_NO_TRAILING_ZEROES) + val_scy.on_transfer_from_window { |v| @scy = v } + val_scy.on_transfer_to_window { @scy } + paramSizer.add(Wx::TextCtrl.new(self, Wx::ID_ANY, style: 0, validator: val_scy), Wx::SizerFlags.new.centre_vertical) + paramSizer.add(Wx::StaticText.new(self, Wx::ID_ANY, 'Rotation angle (deg):'), Wx::SizerFlags.new.centre_vertical) + val_rot = Wx::FloatValidator.new(1, Wx::NUM_VAL_NO_TRAILING_ZEROES) + val_rot.on_transfer_from_window { |v| @rotAngle = v } + val_rot.on_transfer_to_window { @rotAngle } + paramSizer.add(Wx::TextCtrl.new(self, Wx::ID_ANY, style: 0, validator: val_rot), Wx::SizerFlags.new.centre_vertical) + sizer.add(paramSizer, Wx::SizerFlags.new.double_border) + + btnSizer = create_separated_button_sizer(Wx::OK | Wx::CANCEL) + sizer.add(btnSizer, Wx::SizerFlags.new.expand.border) + + set_sizer_and_fit(sizer) + end + + def transfer_data_from_window + return false unless super + + if @scx < 0.2 || @scx > 5.0 || @scy < 0.2 || @scy > 5.0 + Wx.bell unless Wx::Validator.is_silent + return false + end + + true + end + + def get_transformation_data + [@dx, @dy, @scx, @scy, @rotAngle] + end + end + + end # USE_DC_TRANSFORM_MATRIX + + class MyFrame < Wx::Frame + + def initialize(title) + super(nil, title: title) + # set the frame icon + self.icon = Wx.Icon(:sample, Wx::BITMAP_TYPE_XPM, art_path: File.join(__dir__, '..')) + + # initialize attributes + @backgroundMode = Wx::BrushStyle::BRUSHSTYLE_SOLID + @textureBackground = false + @mapMode = Wx::MM_TEXT + @xUserScale = 1.0 + @yUserScale = 1.0 + @xLogicalOrigin = 0 + @yLogicalOrigin = 0 + @xAxisReversed = false + @yAxisReversed = false + if Wx.has_feature?(:USE_DC_TRANSFORM_MATRIX) + @transform_dx = 0.0 + @transform_dy = 0.0 + @transform_scx = 1.0 + @transform_scy = 1.0 + @transform_rot = 0.0 + end # USE_DC_TRANSFORM_MATRIX + @colourForeground = Wx::BLACK # these are _text_ colours + @colourBackground = Wx::LIGHT_GREY + @backgroundBrush = Wx::Brush.new + @canvas = MyCanvas.new(self) + @menuItemUseDC = nil + + # initialize menu and status bar + menuScreen = Wx::Menu.new + menuScreen.append(ID::File_ShowDefault, "&Default screen\tF1") + menuScreen.append(ID::File_ShowText, "&Text screen\tF2") + menuScreen.append(ID::File_ShowLines, "&Lines screen\tF3") + menuScreen.append(ID::File_ShowBrushes, "&Brushes screen\tF4") + menuScreen.append(ID::File_ShowPolygons, "&Polygons screen\tF5") + menuScreen.append(ID::File_ShowMask, "&Mask screen\tF6") + menuScreen.append(ID::File_ShowMaskStretch, "1/&2 scaled mask\tShift-F6") + menuScreen.append(ID::File_ShowOps, "&Raster operations screen\tF7") + menuScreen.append(ID::File_ShowRegions, "Re&gions screen\tF8") + menuScreen.append(ID::File_ShowCircles, "&Circles screen\tF9") + if DRAWING_DC_SUPPORTS_ALPHA || Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + menuScreen.append(ID::File_ShowAlpha, "&Alpha screen\tF10") + end # DRAWING_DC_SUPPORTS_ALPHA || USE_GRAPHICS_CONTEXT + menuScreen.append(ID::File_ShowSplines, "Spl&ines screen\tF11") + menuScreen.append(ID::File_ShowGradients, "&Gradients screen\tF12") + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + menuScreen.append(ID::File_ShowGraphics, "&Graphics screen") + end + menuScreen.append(ID::File_ShowSystemColours, "System &colours") + + menuFile = Wx::Menu.new + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + # Number the different renderer choices consecutively, starting from 0. + accel = -1 + @menuItemUseDC = menuFile.append_radio_item( + ID::File_DC,"Use wx&DC\t#{accel += 1}") + menuFile.append_radio_item( + ID::File_GC_Default, "Use default wx&GraphicContext\t#{accel += 1}") + if Wx.has_feature?(:USE_CAIRO) + menuFile.append_radio_item( + ID::File_GC_Cairo, "Use &Cairo\t#{accel += 1}") + end # USE_CAIRO + if Wx::PLATFORM == 'WXMSW' + if Wx.has_feature?(:USE_GRAPHICS_GDIPLUS) + menuFile.append_radio_item( + ID::File_GC_GDIPlus, "Use &GDI+\t#{accel += 1}") + end + if Wx.has_feature?(:USE_GRAPHICS_DIRECT2D) + menuFile.append_radio_item( + ID::File_GC_Direct2D, "Use &Direct2D\t#{accel += 1}") + end + end # WXMSW + end # USE_GRAPHICS_CONTEXT + menuFile.append_separator + menuFile.append_check_item(ID::File_BBox, "Show bounding box\tCtrl-E", + 'Show extents used in drawing operations') + menuFile.append_check_item(ID::File_Clip, "&Clip\tCtrl-C", 'Clip/unclip drawing') + menuFile.append_check_item(ID::File_Buffer, "&Use wx&BufferedPaintDC\tCtrl-Z", 'Buffer painting') + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + menuFile.append_check_item(ID::File_AntiAliasing, + "&Anti-Aliasing in wxGraphicContext\tCtrl-Shift-A", + 'Enable Anti-Aliasing in wxGraphicContext') + .check + end + menuFile.append_separator + menuFile.append(ID::File_Copy, "Copy to clipboard") + menuFile.append(ID::File_Save, "&Save...\tCtrl-S", 'Save drawing to file') + menuFile.append_separator + menuFile.append(ID::File_About, "&About\tCtrl-A", 'Show about dialog') + menuFile.append_separator + menuFile.append(ID::File_Quit, "E&xit\tAlt-X", 'Quit this program') + + menuMapMode = Wx::Menu.new + menuMapMode.append(ID::MapMode_Text, "&TEXT map mode" ) + menuMapMode.append(ID::MapMode_Lometric, "&LOMETRIC map mode" ) + menuMapMode.append(ID::MapMode_Twips, "T&WIPS map mode" ) + menuMapMode.append(ID::MapMode_Points, "&POINTS map mode" ) + menuMapMode.append(ID::MapMode_Metric, "&METRIC map mode" ) + + menuUserScale = Wx::Menu.new + menuUserScale.append(ID::UserScale_StretchHoriz, "Stretch &horizontally\tCtrl-H") + menuUserScale.append(ID::UserScale_ShrinkHoriz, "Shrin&k horizontally\tCtrl-G") + menuUserScale.append(ID::UserScale_StretchVertic, "Stretch &vertically\tCtrl-V") + menuUserScale.append(ID::UserScale_ShrinkVertic, "&Shrink vertically\tCtrl-W") + menuUserScale.append_separator + menuUserScale.append(ID::UserScale_Restore, "&Restore to normal\tCtrl-0") + + menuAxis = Wx::Menu.new + menuAxis.append_check_item(ID::AxisMirror_Horiz, "Mirror horizontally\tCtrl-M") + menuAxis.append_check_item(ID::AxisMirror_Vertic, "Mirror vertically\tCtrl-N") + + menuLogical = Wx::Menu.new + menuLogical.append(ID::LogicalOrigin_MoveDown, "Move &down\tCtrl-D") + menuLogical.append(ID::LogicalOrigin_MoveUp, "Move &up\tCtrl-U") + menuLogical.append(ID::LogicalOrigin_MoveLeft, "Move &right\tCtrl-L") + menuLogical.append(ID::LogicalOrigin_MoveRight, "Move &left\tCtrl-R") + menuLogical.append_separator + menuLogical.append(ID::LogicalOrigin_Set, "Set to (&100, 100)\tShift-Ctrl-1") + menuLogical.append(ID::LogicalOrigin_Restore, "&Restore to normal\tShift-Ctrl-0") + + if Wx.has_feature?(:USE_DC_TRANSFORM_MATRIX) + menuTransformMatrix = Wx::Menu.new + menuTransformMatrix.append(ID::TransformMatrix_Set, "Set &transformation matrix") + menuTransformMatrix.append_separator + menuTransformMatrix.append(ID::TransformMatrix_Reset, "Restore to &normal") + end # USE_DC_TRANSFORM_MATRIX + + menuColour = Wx::Menu.new + if Wx.has_feature?(:USE_COLOURDLG) + menuColour.append(ID::Colour_TextForeground, "Text &foreground...") + menuColour.append(ID::Colour_TextBackground, "Text &background...") + menuColour.append(ID::Colour_Background, "Background &colour...") + end # USE_COLOURDLG + menuColour.append_check_item(ID::Colour_BackgroundMode, "&Opaque/transparent\tCtrl-B") + menuColour.append_check_item(ID::Colour_TextureBackground, "Draw textured back&ground\tCtrl-T") + + # now append the freshly created menu to the menu bar... + menuBar = Wx::MenuBar.new + menuBar.append(menuFile, "&Drawing") + menuBar.append(menuScreen, "Scree&n") + menuBar.append(menuMapMode, "&Mode") + menuBar.append(menuUserScale, "&Scale") + menuBar.append(menuAxis, "&Axis") + menuBar.append(menuLogical, "&Origin") + if Wx.has_feature?(:USE_DC_TRANSFORM_MATRIX) + menuBar.append(menuTransformMatrix, "&Transformation") + end # USE_DC_TRANSFORM_MATRIX + menuBar.append(menuColour, "&Colours") + + # ... and attach this menu bar to the frame + set_menu_bar(menuBar) + + if Wx.has_feature?(:USE_STATUSBAR) + create_status_bar(2) + set_status_text("Welcome to wxRuby3!") + end # USE_STATUSBAR + + # connect event handlers + evt_menu(ID::File_Quit, :on_quit) + evt_menu(ID::File_About, :on_about) + evt_menu(ID::File_Clip, :on_clip) + + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + evt_menu(ID::File_GC_Default, :on_graphic_context_default) + evt_menu(ID::File_DC, :on_graphic_context_none) + if Wx.has_feature?(:USE_CAIRO) + evt_menu(ID::File_GC_Cairo, :on_graphic_context_cairo) + end # USE_CAIRO + if Wx::PLATFORM == 'WXMSW' + if Wx.has_feature?(:USE_GRAPHICS_GDIPLUS) + evt_menu(ID::File_GC_GDIPlus, :on_graphic_context_gdi_plus) + end + if Wx.has_feature?(:USE_GRAPHICS_DIRECT2D) + evt_menu(ID::File_GC_Direct2D, :on_graphic_context_direct2_d) + end + end # WXMSW + evt_menu(ID::File_AntiAliasing, :on_anti_aliasing) + evt_update_ui(ID::File_AntiAliasing, :on_anti_aliasing_update_ui) + end # USE_GRAPHICS_CONTEXT + + evt_menu(ID::File_Buffer, :on_buffer) + evt_menu(ID::File_Copy, :on_copy) + evt_menu(ID::File_Save, :on_save) + evt_menu(ID::File_BBox, :on_bounding_box) + evt_update_ui(ID::File_BBox, :on_bounding_box_update_ui) + + evt_menu_range(ID::MenuShow_First, ID::MenuShow_Last, :on_show) + + evt_menu_range(ID::MenuOption_First, ID::MenuOption_Last, :on_option) + + @canvas.set_scrollbars(10, 10, 100, 240) + + set_size(from_dip([800, 700])) + center(Wx::BOTH) + end + + attr_reader :backgroundMode + attr_reader :textureBackground + attr_reader :mapMode + attr_reader :xUserScale + attr_reader :yUserScale + attr_reader :xLogicalOrigin + attr_reader :yLogicalOrigin + attr_reader :xAxisReversed + attr_reader :yAxisReversed + attr_reader :transform_dx + attr_reader :transform_dy + attr_reader :transform_scx + attr_reader :transform_scy + attr_reader :transform_rot + attr_reader :colourForeground + attr_reader :colourBackground + attr_reader :backgroundBrush + attr_reader :canvas + attr_reader :menuItemUseDC + + # event handlers (these functions should _not_ be virtual) + def on_quit(_event) + # true is to force the frame to close + close(true) + end + + def on_about(_event) + msg = "This is the about dialog of the drawing sample.\n" \ + "This sample tests various primitive drawing functions\n" \ + "(without any attempts to prevent flicker).\n" \ + "Copyright (c) Martin Corino (adapted for wxRuby3; original Robert Roebling 1999)" + + Wx.message_box(msg, "About Drawing", Wx::OK | Wx::ICON_INFORMATION, self) + end + + def on_clip(event) + @canvas.clip(event.checked?) + end + + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + def on_graphic_context_none(_event) + @canvas.use_graphic_renderer(nil) + end + + def on_graphic_context_default(_event) + @canvas.use_graphic_renderer(Wx::GraphicsRenderer.get_default_renderer) + end + + if Wx.has_feature?(:USE_CAIRO) + def on_graphic_context_cairo(_event) + @canvas.use_graphic_renderer(Wx::GraphicsRenderer.get_cairo_renderer) + end + end # USE_CAIRO + + if Wx::PLATFORM == 'WXMSW' + if Wx.has_feature?(:USE_GRAPHICS_GDIPLUS) + def on_graphic_context_gdi_plus(_event) + @canvas.use_graphic_renderer(Wx::GraphicsRenderer.get_gdi_plus_renderer) + end + end + + if Wx.has_feature?(:USE_GRAPHICS_DIRECT2D) + def on_graphic_context_direct2_d(_event) + @canvas.use_graphic_renderer(Wx::GraphicsRenderer.get_direct2_d_renderer) + end + end + end # WXMSW + + def on_anti_aliasing(event) + @canvas.enable_anti_aliasing(event.is_checked) + end + + def on_anti_aliasing_update_ui(event) + event.enable(!@canvas.get_renderer.nil?) + end + end # USE_GRAPHICS_CONTEXT + + def on_buffer(event) + @canvas.use_buffer(event.checked?) + end + + def on_copy(_event) + bitmap = Wx::Bitmap.new + bitmap.create_with_dip_size(@canvas.get_dip_drawing_size, get_dpi_scale_factor) + Wx::MemoryDC.draw_on(bitmap) do |mdc| + mdc.set_background(Wx::WHITE_BRUSH) + mdc.clear + @canvas.draw(mdc) + end + Wx::Clipboard.open do | clip | + clip.place Wx::BitmapDataObject.new(bitmap) + end + end + + def on_save(_event) + wildCard = "Bitmap image (*.bmp)|*.bmp*.BMP" + wildCard << "|PNG image (*.png)|*.png*.PNG" if Wx.has_feature?(:USE_LIBPNG) + wildCard << "|SVG image (*.svg)|*.svg*.SVG" if Wx.has_feature?(:USE_SVG) + wildCard << "|PostScript file (*.ps)|*.ps*.PS" if Wx.has_feature?(:USE_POSTSCRIPT) + + Wx.FileDialog(self, "Save as bitmap", '', '', wildCard, Wx::FD_SAVE | Wx::FD_OVERWRITE_PROMPT) do |dlg| + if dlg.show_modal == Wx::ID_OK + canvasSize = @canvas.get_dip_drawing_size + fn = dlg.get_path + ext = File.extname(fn).downcase + if Wx.has_feature?(:USE_SVG) && ext == '.svg' + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + # Graphics screen can only be drawn using GraphicsContext + if @canvas.get_page == ID::File_ShowGraphics + Wx.log_message('Graphics screen can not be saved as SVG.') + return + end + tempRenderer = @canvas.get_renderer + @canvas.use_graphic_renderer(nil) + end + Wx::SVGFileDC.draw_on(dlg.path, + canvasSize.width, + canvasSize.height, + 72.0, + 'Drawing sample') do |svgdc| + svgdc.set_bitmap_handler(Wx::SVGBitmapEmbedHandler.new) + @canvas.draw(svgdc) + end + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + @canvas.use_graphic_renderer(tempRenderer) + end + elsif Wx.has_feature?(:USE_POSTSCRIPT) && ext == '.ps' + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + # Graphics screen can only be drawn using wxGraphicsContext + if @canvas.get_page == ID::File_ShowGraphics + Wx.log_message('Graphics screen can not be saved as PostScript file.') + return + end + curRenderer = @canvas.get_renderer + @canvas.use_graphic_renderer(nil) + end # USE_GRAPHICS_CONTEXT + printData = Wx::PrintData.new + printData.set_print_mode(Wx::PRINT_MODE_FILE) + printData.set_filename(dlg.path) + printData.set_orientation(Wx::PORTRAIT) + printData.set_paper_id(Wx::PAPER_A4) + Wx::PostScriptDC.draw_on(printData) do |psdc| + # Save current scale factor + curUserScaleX = @xUserScale + curUserScaleY = @yUserScale + # Change the scale temporarily to fit the drawing into the page. + w, h = psdc.get_size + sc = [w.to_f / canvasSize.width, h.to_f / canvasSize.height].min + @xUserScale *= sc + @yUserScale *= sc + psdc.start_doc('Drawing sample') + # Define default font. + psdc.set_font(Wx::FontInfo.new(10).family(Wx::FONTFAMILY_MODERN)) + psdc.start_page + @canvas.draw(psdc) + psdc.end_page + psdc.end_doc + # Restore original scale factor + @xUserScale = curUserScaleX + @yUserScale = curUserScaleY + end + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + @canvas.use_graphic_renderer(curRenderer) + end # USE_GRAPHICS_CONTEXT + else + bmp = Wx::Bitmap.new + bmp.create_with_dip_size(canvasSize, get_dpi_scale_factor) + Wx::MemoryDC.draw_on(bmp) do |mdc| + mdc.set_background(Wx::WHITE_BRUSH) + mdc.clear + @canvas.draw(mdc) + end + bmp.convert_to_image.save_file(dlg.path) + end + end + end + end + + def on_show(event) + show = event.id + + if DRAWING_DC_SUPPORTS_ALPHA || Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + # Make sure we do use a graphics context when selecting one of the screens + # requiring it. + # If DC supports drawing with alpha + # then GC is necessary only for graphics screen. + if (DRAWING_DC_SUPPORTS_ALPHA && show == ID::File_ShowGraphics) || + # DC doesn't support drawing with alpha + # so GC is necessary both for alpha and graphics screen. + (!DRAWING_DC_SUPPORTS_ALPHA && (show == ID::File_ShowAlpha || show == ID::File_ShowGraphics)) + @canvas.use_graphic_renderer(Wx::GraphicsRenderer.get_default_renderer) unless @canvas.has_renderer + # Disable selecting Wx::DC, if necessary. + @menuItemUseDC.enable(!@canvas.has_renderer) + else + @menuItemUseDC.enable(true) + end + end # DRAWING_DC_SUPPORTS_ALPHA || USE_GRAPHICS_CONTEXT + @canvas.to_show(show) + end + + def on_option(event) + case event.id + when ID::MapMode_Text + @mapMode = Wx::MM_TEXT + when ID::MapMode_Lometric + @mapMode = Wx::MM_LOMETRIC + when ID::MapMode_Twips + @mapMode = Wx::MM_TWIPS + when ID::MapMode_Points + @mapMode = Wx::MM_POINTS + when ID::MapMode_Metric + @mapMode = Wx::MM_METRIC + + when ID::LogicalOrigin_MoveDown + @yLogicalOrigin += 10 + when ID::LogicalOrigin_MoveUp + @yLogicalOrigin -= 10 + when ID::LogicalOrigin_MoveLeft + @xLogicalOrigin += 10 + when ID::LogicalOrigin_MoveRight + @xLogicalOrigin -= 10 + when ID::LogicalOrigin_Set + @xLogicalOrigin = + @yLogicalOrigin = -100 + when ID::LogicalOrigin_Restore + @xLogicalOrigin = + @yLogicalOrigin = 0 + + when ID::UserScale_StretchHoriz + @xUserScale *= 1.10 + when ID::UserScale_ShrinkHoriz + @xUserScale /= 1.10 + when ID::UserScale_StretchVertic + @yUserScale *= 1.10 + when ID::UserScale_ShrinkVertic + @yUserScale /= 1.10 + when ID::UserScale_Restore + @xUserScale = + @yUserScale = 1.0 + + when ID::AxisMirror_Vertic + @yAxisReversed = !@yAxisReversed + when ID::AxisMirror_Horiz + @xAxisReversed = !@xAxisReversed + + when ID::TransformMatrix_Set + if Wx.has_feature?(:USE_DC_TRANSFORM_MATRIX) + Drawing.TransformDataDialog(self, @transform_dx, @transform_dy, + @transform_scx, @transform_scy, @transform_rot) do |dlg| + if dlg.show_modal == Wx::ID_OK + @transform_dx, @transform_dy, @transform_scx, @transform_scy, @transform_rot = dlg.get_transformation_data + end + end + end + + when ID::TransformMatrix_Reset + if Wx.has_feature?(:USE_DC_TRANSFORM_MATRIX) + @transform_dx = 0.0 + @transform_dy = 0.0 + @transform_scx = 1.0 + @transform_scy = 1.0 + @transform_rot = 0.0 + end + + when ID::Colour_TextForeground + if Wx.has_feature?(:USE_COLOURDLG) + @colourForeground = select_colour + end + when ID::Colour_TextBackground + if Wx.has_feature?(:USE_COLOURDLG) + @colourBackground = select_colour + end + when ID::Colour_Background + if Wx.has_feature?(:USE_COLOURDLG) + col = select_colour + @backgroundBrush.set_colour(col) if col.ok? + end + + when ID::Colour_BackgroundMode + @backgroundMode = (@backgroundMode == Wx::BRUSHSTYLE_SOLID ? + Wx::BRUSHSTYLE_TRANSPARENT : Wx::BRUSHSTYLE_SOLID) + + when ID::Colour_TextureBackground + @textureBackground = ! @textureBackground + + else + return + end + + @canvas.refresh + end + + def on_bounding_box(evt) + @canvas.show_bounding_box(evt.checked?) + end + + def on_bounding_box_update_ui(evt) + if Wx.has_feature?(:USE_GRAPHICS_CONTEXT) + evt.enable(@canvas.get_page != ID::File_ShowGraphics) + end + end + + if Wx.has_feature?(:USE_COLOURDLG) + def select_colour + data = Wx::ColourData.new + Wx.ColourDialog(self, data) do |dialog| + return dialog.get_colour_data.get_colour if dialog.show_modal == Wx::ID_OK + end + Wx::Colour.new + end + end # USE_COLOURDLG + + def prepare_dc(dc) + if Wx.has_feature?(:USE_DC_TRANSFORM_MATRIX) + if dc.can_use_transform_matrix + mtx = Wx::AffineMatrix2D.new + mtx.translate(@transform_dx, @transform_dy) + mtx.rotate((@transform_rot * Math::PI) / 180) + mtx.scale(@transform_scx, @transform_scy) + dc.set_transform_matrix(mtx) + end + end # USE_DC_TRANSFORM_MATRIX + dc.set_logical_origin(dc.from_dip(@xLogicalOrigin), dc.from_dip(@yLogicalOrigin)) + dc.set_axis_orientation(!@xAxisReversed, @yAxisReversed) + dc.set_user_scale(@xUserScale, @yUserScale) + dc.set_map_mode(@mapMode) + end + + end + + + class MyApp < Wx::App + + def initialize + super + @images = {} + end + + attr_reader :images + + def on_init + # Create the main application window + frame = MyFrame.new('Drawing sample') + + # Show it + frame.show(true) + + unless load_images + Wx.log_error('Cannot load one of the bitmap files needed ' \ + 'for this sample from the current or parent ' \ + 'directory, please copy them there.') + + # still continue, the sample can be used without images too if they're + # missing for whatever reason + end + + true + end + + def on_exit + delete_images + end + + protected + + def load_images + @images[:bmpNoMask] = Wx.Bitmap(:image) + @images[:bmpWithMask] = Wx.Bitmap(:image) + @images[:bmpWithColMask] = Wx.Bitmap(:image) + + @images[:bmpMask] = Wx.Bitmap(:mask) + @images[:bmpWithMask].set_mask(Wx::Mask.new(@images[:bmpMask], Wx::BLACK)) + + @images[:bmpWithColMask].set_mask(Wx::Mask.new(@images[:bmpWithColMask], Wx::WHITE)) + + @images[:bmp4] = Wx.Bitmap(:pat4) + @images[:bmp4_mono] = Wx.Bitmap(:pat4) + @images[:bmp4_mono].set_mask(Wx::Mask.new(@images[:bmp4_mono], Wx::BLACK)) + + @images[:bmp36] = Wx.Bitmap(:pat36) + @images[:bmp36].set_mask(Wx::Mask.new(@images[:bmp36], Wx::BLACK)) + true + end + + def delete_images + @images.clear + end + + end + +end + +module DrawingSample + + include WxRuby::Sample if defined? WxRuby::Sample + + def self.describe + { file: __FILE__, + summary: 'wxRuby Drawing example.', + description: 'wxRuby example demonstrating and testing Wx::DC features. Adapted from wxWidgets sample.' } + end + + def self.run + execute(__FILE__) + end + + if $0 == __FILE__ + Drawing::MyApp.run + end + +end diff --git a/samples/drawing/tn_drawing.png b/samples/drawing/tn_drawing.png new file mode 100644 index 0000000000000000000000000000000000000000..1079327a0246fdbe6796f74c474959610e1a0c87 GIT binary patch literal 33630 zcmeFZWpG?evNbAZW=30NF*7rh#nj>!TWT>gOP0lwMHX9RScY9Y zb~lzfW^wwoWxlruIhNCI?eC(IMw?xg+{a$nv7W=Ve25EIL4P z+r26iv1U)SqfO9Z4Eph?(#EdF%PXkTuwv}a5U}o>Yq)h{>7n?#aaM`el31^F?gNWU z-zMe;gLdu9o^wTYxfI+P9z)uq^o)`tKuJ!P)pPKIx|f=p)2pQP$oT2(%%^|WPASS~ zSmSz(b|aK*?`}>hmM~dxY!LY+l&|lx`D%^y&fjYd{RPeY#dq}Qo*ZNV|s9n58@C#sRe4wchwo zXD_Baz{30rX943lQil(!k}!hmqxSu z0ZC0|U?{LZ=)ou)R_|=g3=#vGM*BD{ENpszi4oSon8xQt-iF0@pnd2e^F|`~^iQ!H z(Tr#+eB9=-;YLY`3&(P#*)9lH{B0~htrVU}a7de%qB>Zdm!@;bvz?+g+FOL>>I|byjg!K6uAZRY zVcU{t8)H4PsbXsDgb>m&Fq@gtShG`;&vm zU%_fSDa}5QBq-9KX>VE5+9x{%#OHmFW7g`9yZC{nJZ`{KHPf@7$g?xdsigT~j4a-< z_B*R2%$AEF@m&(%mLdM<-Pg~n^5JVcBX735QL1;LsZTuiS!TB3A-f~q zOO-9t+e3riu5_&-4S<6ME6!0|#Hit|t%IKYnv>!K&?)xlFV7?=P!HBnCFC(5U@ z@rvZFMppS$pp0iCiL13B7m3R*CSAA=zmAhLwKFiCx_ep?v@A5NH|kP+EHa`@)|Ii) zaduy9ISq~aymS+>(`BjLJzVqcGy?0vGzTt0 z)t)gv;W(IOhPFGefk=ku0rU*%569-M)GDCxte;H#IGMM;D6E~o`N+Rbo;uEXzb=J0#K63XE#; z<=&F>!p*>FzJQ9<^upFlGRx(Z_A#Ubuy{&{*NMjPbCcza5^hV;LnhF;%Y(ozyG8wm zlV0xQU5WqKudCnnuCGruO8jbmf0^C9it9Y&pAGVPK30y0gc0Eab&DYFE8q;Y?4 z(;W95!LNK}Z{aUrMpWGQxnOEkE3-Me?^l6VBa z9lKhZF*Z7Xj@HL1#Br;uJGN{4(PFd)*9lEWe7F+12CbN}?5BGd;3fJL-X(kf#*lo zT?^aC-0(!N@;@@tm%;j4lpTygy`sWr@Bq9HiUY7BTU4wbywJgj4(}BaiY1X-dc|PR)99Z z{6UCU5~LG2DnS@J3%-m2b6GXoeHGZ!uOv4XFa|H6_0_s)JL>!@UT@rPAy#t76>E*N zr!a~p#*?C!PwUKM{DvRbrsz_!$$~NKCmh6eMSo+5<0<4E$QdblSk;!3Wx#;Q+Omkn( zaYX0mJNm(4KGu0Eca62?Kz{g0tB}hub#asO(g^EC;jCB>CUfhdR8+0qBfe{YI!%Ua^o!Y7?k(h;@qRI0J3dUo&#kJ8V(2d_BU5mDzIvJ z4%E-!D_y0BiDNITqEA3*3<=)Spi%t zFcRWlm;Daep~UW0=aGlL*~NxSepLYxX(`Z@QacpxKhtm)=+gXr24I8dJ+%|qls4)e zbmcbvZu8KkF8pbfFT!HNZ#xa_WpFnQC%$%@-Rws=(82PlC=#1M-cE~=hYXg5rJMV< z%Pkg&!TA`gqeIAPu);rIB(rB{tj--orv19HIJ60{j;^GR)x2K6Q1dlv_qGsfO(R8Z zHfW=1|Gin_(-2R0Uu<}#(Vn29lm*6Wg85-9lveW2o#iLah*}g943`hW!jHA}yI{`L z?hq2_@E^+?3C2fW;o@-_DYf6l`(Md6<;!G7T?4SzfdwnEQo9fg_$8zb^{0{9kWZ)( zu!T*+awH~1Qr#Dcbqyo-X*#MH)llTSB9xTTE=m(-UUf)SglxunoY1KorOn}f>8w@b z=p6Iy?p{h={h{AJNo)%QB_91Cb(OYsY9HtYdSyZ zyMkQ&8S>eGAwi1aTWggFBX^HT-DoTNbcsZ%(cvd8?PvvM6o)BoBSSPR1#`A9S57*q z%(9a!=^|rXaF8^_|AKBl84Z^ws^*J|2<6})7YE9EBYGoRDSy%wAF;-P`HU>PUMWcq z=SG=Au&y2MA}IuBIK90bQW=Br+vIouH@#eS0Uw!0kcICZNESWmMW`i5sX&=~NX;SW<6^gcuGQ3AQd-kNG-wjeI!*;G^_t?o z$`lfQaz~q<%fe*hdY6qHb`JA-TKuwcoK}SR5hu0s_G0ixfEySk5 z8`oXhf0diNoDTAFpZ1tYIXV4GR#zw2Cb}XK4jl20}{#c;1k;i7Go#OL1M;TEsU4exN7wkN^j9QH$xH^zmSdYSsx z4>vB>xm5h7d+oXjP*^a$u|r9#5fL)R`6{Ac)v0bXHI2GTvuQ3b;}W9pggLr3aUd!T z=2?Y$O|WO3wt&eqNKortSY;%2h%f!8gf86CP(}sB zC7hF-J5)lRAnag?JL6uBe^VlYQ~}ev>6OAO?15azNhZKV!H^4_P@5_M6J8WHuwHKX zNrwjf>cCpJ?SD-Rf>4vB-6qwAdLDmeu}Z}X$^pRM!_Bjmhf~s~WoC~#o~47w*Cf*5 zD@<=TF{i>AWVdx8r$NyfyW3#2>(y@`xOA!1vlJEG$Rmswq*Zc#SlJ9MqWVs52(0GH zR)Z?QQW!-mtp6f+f`Loj=0C4hW$hg7$;%L+lPf9~mW8i3o}e)GD8E-JFp@C=*0^nx zYxFB3FxzRmkuK&p)5nywgeRKaX|m^~`r2+~X;ywNqF;yLU?_Efm1Q-x-pW%CW<(om8-9}RU_>;QFhW(ORor$ZSkoA;FK2qo*L zMISi(Ba~9|W0KML?U0$M&CtaCAHki2tQHsAuO8YBkpZRf6Q`D0BGkR|t46_!)7h_> zM#AGLas12U&wQsO1=szhP&oL!Zw{V^aHakDa)NVc@m4C9+QxRD@?{5DaI`RNOC9rr z=}~-j^lIjF@tJz!pBTGT%`yYAs86)zIt`}m$sECmXqC^UewP+T3Bt$8XAAc zETnR_!5G66Brwd;e)EWyU>*wO|L#Me^CF5d>);9m>)s{^>I3c+xj-GUEvD^(2q4X` z!?|1Fj&-EdladFP;1U;bO)-(+<;W>?sw^zgj&)Q)MYvdpB4eM$e{3qWO@J|Li1%*# z2@G_y^uv~a&0WCyP4;TZIbE(UO(PJfMULY~(P@U+Gj5)rvYiK$Qt-2##{>?=4ij`< zz{Io?3c}yVg4I!=VWRJi%bAQTo=^10DrGny1)Nb%XFm{Gf|O|x9Y7&^mqc5Zt%88r zxX!R3JSYro&JCeG`|K$@@sd~3k14(;LP9^Guk4TaxbEC7tc!4Bb0=ln{ZqCEM{2w< zf+&@zzKddCeQxHBw#-+@mgt}+6CMBMIx0LBEN;7MfYvTX^{6pL zba+WNopk^+`q$5=i@Ty-=DodLpX*}!6*0>ek+T;<5rm1Cj}6!nlOe4n1UC-o(moG) ztqN#o8fIVLk=eeI+ImNtU|tqkMqk|!zh@awbCwPspenqsy@qT5gW`SfV%1lw=j^yW#J{tiDe zF65p4WG1;+OElwgYB)QAusRl2JCz%I!qpp7oxkEfrQrDH955+N4Ui>E44tt~06l@; z=0;_}KqRqs7tVhikSSox3p0M~u;FlaI8Y|EbolWJlYp(I??W`*8ZNDC8N&^?^_S(h9ewt&}X>dxSS#4uqM8+GF zTq5h2d_t-5lc%NdaLoc)Neq)bria>Ij!EY6ku8J;v>T$4st92R#IL*P4E9ABjBt8A zw-}eaX*8sciENCqbTp%y@3Gd5tPvPufg}ENF*S1gOh~O+jHv|UT8#p1f zLI*Z4rtdRSB-!rt(r!0%fO8+PRi{!F(*4fr)AHXyTPr<)IZ;@2w;mg9Czf%OrcRKF zgc>Qz&&EGqSDUaR+*cL}h0FybL+bn$P>i)4xD{xVm{py^hIC!P++|#4uB`RGb;= z*eS=JLr7&^rL0=q_MDEYAoWF*CkCRpHm|`>n{)^2M(UHFA?zT~E8qdTu38p5uwaxW zh42MtpB+-JYm_N#o#As(MI}vB4zUa#U`$(04&gG_I-vuv4_cd15$jgcN6J=64BsTm zO%x0n2xDMtiKF0;@Plwai@$Dm2hmLB{1}eLeHeK^3TaXiRt-t4TU6P3&8>&I0b;E0xk z204A22itCZtU*D`4@P8AjMq(bFTIT!N5hhvpd7Nr+&?ODU~Nm_-~$YYirYw&UEG0C zbuRV+7oDa*VAWDXDvUQxneX<`UsA(UAj8b}?6xdI?Cx68F#`WM5c3l_D19<%z>Q=N z2l_VoW4@+nRmCceKkI%5iS;%CgHGv5DGa=mZ5^O;$8)@t+u!IwT|TnMw%{vRW!q|y z^Ev~;Q7KvGn~=4;(wi=3_Mi7M2-Jcr|vB->0%~igBwC!do_p7$zYpQKnxs*Kx zZ`w>0F?LEH9BV(=uUPd@m+8OD;kXcF={2k84dkTkz8MP9CfQVXhpQK3gozYb%(qrA zi>l1on!TOOp0?qOR^n@c*;Z>PyqM?!{A@UKEPsjxK=pR+b*p$vBVZ7d3ExGnS99+b zhpXpCcWZ=Pf~sxnJ@`kni(H-;t91r;27xwS=rH&-Vt4DuZeh9DTeU9YOu{d&SxR6` z+h#5zuv42fteVlL2nxlYg@=Dr>59@aSZY5SyFYo`E^A&zH2jB>SG(&46zkRr5kxmk;s1EN!}&j}NJcqA;u>5*MAW1C%~`0U-g9dPm@5s4_oG zh(Chv4(7_+;oMHe*J#tM1@vq!Zz4VT7ug0+9CJg~o1vr<+h`;HRlh5CcY)dJk+ua=iI659m~`qdnDPOdWi*BS-_ zpsyFlwomX?$P|8&%8c zWuvI)LDkfm!6@^Hx_yq$`7Fn93OX6_d$WkS%^qvAM7j0Puc0lMJzTb!cBnnoIO`K2 zAOzJ(`YRd_la=oU(pGE7k!23o-zfQpgc;b3i-{q+Sk$mJ6P1iC*%DHe?f-E7wsUq@ zvE%*{-?i)f%;An^Fk}x;kUZ>JF#*M{z)*U6Tn(=Mgeefz*g4Kyw!GB5bcKIEy?P1R zN-<#Mjlg5o0ZAQ*GC%?V-A(8E)2)Pi?(5`>o;kF%F#GpnQuIf3!M(4VCPbt#A=8qH zQ<#bR#(#R3>kPUS+3ular6Nb{TDD!;S72b*zM(O2%+7z7Zrtx?|BI#dKS3fA6jl?KvBNCSLre zUuXF}{FV2<{30z!<*MX~MJ!FZN%LxPIpbjB=Sy+9Si8SeX9HwIa?(quWcBI>XiIkmD1U76q3?I(z_xqh=|HKyIx&hcf&|rs;T6>< zB-y8OL8PNX5#ZewTWM@yBRp$6qDe`=TBBM|p)EwV)=N@{cP3Iux*eUp0<|ypW}Kox zN|j0G6hM35`CyHno@?`qk(vh~XK?99-;pV9^0T-Y$xmwt>gYLRBQzE9l>Qy8pp%Cg z3PfDBVS4lG?P}>_nQpSQDLR2IB`oUET5V5|rd7U42rL z+l7Hr-_pGA49^W0mPYvMCn8Q)x-G4jx1R^$SFHo&FZUFq&q+|FJk zv}Y@(UOAbsqK=1tBN^88vC-t-4@Ht6s_ZRmAqZKp^48P`n8@(uD-3W8v9{<>ZC;b( z3x!Gw7C$aWztT#PXhIjS$K0x=zriA9>V7j*AOnJdfd>J_#FS;k#QwQg^}a*(-8)V| zx=Wa-$8aj*Go8dS+2+?AUTrk&FVl2bIif|_I?l`u*NAECA7~k40<1eaVtalDMpl)h zln0RAgKr+M=j~H`d}X4o4!c>eCp|kAI9&x7Z5d|&#EfFuXj_2sg(Y4eyU7q;SjYoS zQrSF-g<3`iL}Yk(esROtTg998kFk*KI{c;X-pAb0nC)*!dIt=?`6$C)-wdpQ6O?4l$a;Ep>}II- z%iu6sNDB%214?TBd;WK9j|KG14v5#~?kya5Umn5oic7YU9US1IaA-VRx|#w{T3$R0 z7S>SjXB=G|^@{1=_Y?_$?|X+@3i7-r_O^@wQ+s1GMmJlB_q|0hFn%F72Y`vS8Hmi- z%mQd9KylX8MnMKN6`;`MP+(SY5Hqs`N_#k(sd*@>n|N58@R(8v2_o>j@xBAtnt=dh zZniddPP}dc6o29Jz90W7W}+bbO9W&sK%u3eOeSXUXhz1)$j->jAmIjdVWkj6Amev5 zHRn|om;49B`dNTK#%S+o!NkJD!^6bP%EZdb@GimN&gDER(94lfAQ}iJ63pnH`Ao-yuv*{-y8W>}c~> zIHo2{W;SNF@1jocR$2bTq?C+;^1n3xP+$SHb@)r`o$UXR1Od(ehphkb?N7~L;rzQJ z@9O`;{SWDX#{QS^yOe?gueiO5^B?zQ#04n+*ylC1HvyXR{&fg2HQ_PgVB=rBS7)TC9*%7 zf2B7W|KE)w4Rm^!aQ~C>|HyeYvoC+2{oMs@fPYnyk^Pmnya1EGjW_{Z%uN3>^se`J zm5C+5&cf`yfBz$){#g$EKQxvZD>E|(4-Yc~4*3cNq3G`PqWHf(KN&BDDu9jwhcw%N{XJBS$VBu0{Vc})r z;ALm|#LUXe%uK=bkARu}^y+^`%+K`y;)MS%fqxqY-u3=2dmmojM=Pd(4Ojo*><^9q z7jOS)i~oxj-l6|%Zv{#U~P&948_^}k}^eW9oMBmVOx>5}atq&IK zN(a4tA8rLGPH#fp66MUyU2&VNk79(B-Lx+@C6>9lUhlqpp7QJ=+=h|~`+<#pnl^xq zR(kAdaN6tNaqQmul5^xi)(Hllsp6I?Ri>eKbado(zY!W+-s8KY=_Bz8Co*l%9gaL` z&)r`g63R_7=ZII9Hvfn>RbKM-^z60#c{u28A?roE#h`-FZ`VE3L0{7{Eu;YGZ~89X zOO`J#8xFu5WJbzQ_*SVk{1PwpfO)0{|8^zxcBNCJQ$y4T4_Tlp3(203B?=dW*0C%o z>}O&2<8%w0>mymBY779cOpAV(U;dGJ`z6@$9=c4mSZl?OE9vNLj5@wJB_WeGe;P`Z zpW848ViFea9O+#Dnxf@#CQO_ns0*-o!YK-;H)m#Ri@a1DE}3g%W5fJ) z%tVVS5;5}n=BBc?ws(9SO-@d3zcB_aK#O0oMwL8cK$#9_f;}3HOs)6EWY0`!6P5`N5AM5r zxc%mAeEp}5jIL+PJ2zYgd#5W+>4CS9AL3=o4O#}r$H!yV;DeN{>YP^Qhd}ds9+&~g zOG1NfZ=?jJTpxN4hlqZw@)5+#=HK5KzBzvzFdcVN!W%ICMmSgDbiNK%e)LY7a{GBd z^M*r;Je3#?){g}%^jh1rGMo?qvyf2V{5-cP+&jaGvcV4A@)U9V$Y@jrz~7aX9i5#J zVq#)$nR1m{#zsbDXwN3?l_q^f8YNAo)LqQn&X4bXEryt{cBq;uQTBNp^#yImF;%fJ z&~_>P`O&c@SjR}^o-3{jvee}hAK^}vyek3hqn&-jhE-(fVC6NXU=`!SL0 z&8=J?TZ1K;!PD+!8VKYQF&Z{@)*RJ2@S~1heVsfq+o#D(prO_Ng!Of@>*@nd@Sgh< z3?lvT^3LcqyTMsA?1JE{{rvMu;MuKclfmZW3eR_!#38OiPg_-*{Gzg9);2w#ZB94U z%ZunwZY^zhT`%dHKwjOkHVqmwfwx!BoZqe8H-ua%23iudGIeT&T0px&$Fg7k*UjE% z3#G{opaXuyPe-T&YfI!M!A)EZZND1buD=Yo+v7?KV zB|EP1PM*`1`Y;kwQ=eNp$_DUVjY#pXFIG1Q7ohLa(Tu5KBMn>z3mj(-yvTyBf(lwx z+Kjc9KGiW)(BbvjQ#P0p91o)-h##CVn;j3=3#$QPMY9`^Ej$#(czbJagg^BINqwFe z;pxedL;_d5kP9nA@pTp!?hn=#PCL&}%}s6@{;xhs zyCveP@TMifa59WO%M^fx=G)t&CZbPqS_oZV>qpEOm&@7EE>d)K#O~6>tC@>UQ=I#H zZ9cP<(aq68+vx7Cwn$uR6KhI3Fjvi!Wx|;(@v^afdaBLQ z*Ulq6*$b{DN@n!(yM&5MkomHMU`-yayj-vtgZ8sbEz!)enCK42y7^|-TD*)*^a&XE zvXO90{Kkf4GIl*@^(7Qa1FgFiVMpCM8e=HW@G9{N&q(J$klwKrrS7+kB9(INR1O%m&V_EmN z75mp*QhhzW6o|oETi2(jr-S62*6M|CN&Az@S4uPP~%0eTAqckeYA{NCBK zGFvE?kyUR&F*9QF>QI9`+qr`mgQ;ODyRO|zZvzj7t5`(u!hONw-7GYM*#e}Ot7C^K z6KbUkZCn|WWml$$Bh-~Q5#NKQLds4dCni>F&x|%$P}j12)-of@(B!R6^3Av>=K4X` zd^!@$fjTQ*>ihJJk%Rw9vG|U>S&<*-W05&FS-TT((gg2GtH|FC0_y&djLgdq415C; z3Jwj;hp;gNXGa!$?#iVszG;QPI)C?88+t}IRmY@6{@XVaRUZ0MJAP;Kdc|bbV)6Xn z9nX^xQ;~A&hYGSg2&=WBSNyRvH)eVdHlWNYTQ$1G{l-=5;XVGre2g+h*FLBE6@rp{ z$#%aAyw};!z_o|dIv$5-B2A_Yi{t$-NxQiHdz0#P$dZ!AdM#=em*Z$PGZGXF^qNfa zIeZcZFTG2{V~mtdW)u%!MtwoyxJuvGuLlMSse)z}(n~>)cth^;W6OY=d;m8NCVcpQ z7TGfG4+#prmReCdm5&(2oBJ2>c|Vj~7Y<%WKGo-dn==^zjA>*)2ny$F32UjShNCGc z_0cl1RUL17U>opwf4{Mc4?fV2X=Cn&O`hZSbo@pMD+NIXMerJbaoAAT;CY2$DiPAv8F$!1H=M_LlZlcW1DZ*SZ9Hu?(hg)h+$;k4vU5%1&UYN*S`q-XWp2BcsKEj&J{srTT_w!NBY{WuMYnYrmM;5Cl|^uPIHwgMPRw zYO^jx*Z4&Bo_w;662Dtaz}HsxOVo24Odsc(&SgD#1;~7MW0t`De27$e7+L{d;#e=i z(R(+Sv7n?vlrb^&!!So=0*0Wi#|4`ZlnpdGe6f5Cqx3OTi}G@fl%|>>gHq9ah2?Mb zWpmPnoBd%5d?fjA{a{=K9hfkZsKc+6yE&$%0mH4;c-TO(Yk-Uahd zHk^{2geJCSgM4T5Urv{y3LdTzK7>gko{ZA7>%=m1+hpSo_KQ&#>FpM~tlz|1u-K1X zh>5WTh50l-$8#=xC|$|eSb4H;el%g?|NH>!_WNGXWhEG~>iVva$HnG64%7DW3A6cf z5Pa3!HNtMrlh?FjdZK*w<=Au$WlpR~AhPz?85Pme%II!*QHG(n~Sgo+!%M z%Y+oDNkpK_?>wtOyPS!YH?5%U&Ax3c#g?%7qL*CoetosIW{;%Ayv?0-d5mj5EY+-A zO)I|5$2|$M2l-LW_vxg?Ms2U``#|Vg>vQ)G={gU^bFU;ZdG{X06QovBN3j~EUR&$^ zcBYPObyHPV`f<8wb6c1tf5XaWF3v+dE(;;t1+E=?%Vx_ay*7T_U*u%ZtM$&-i6(um z+@VUm#5qehmNl93%^6w?ejZ1N#KPUf7)n3Ik52AR9c;L7dj^EeHw?1!XIvm8p@P=J zMRP{ijh+z=rv$zjhb`45XGOUf>O5`2#;Ibf*bCn7;vzVQEJh0;+W3%sCyH`-I`nD} zW8xuMkP{NnUq4=0A;hj-S`n!KcmVn?&{jonA_c#q+@H7NqH=iATOVcxd22alQJi~s z-d=gM7|cpX;$2G2>1`dSdD(O}vD$S(mH%U_-qXiI04oghH;9+bx+obHCVvE?^T|!P zH#CqlFF~&nlw4W)s?VfXtWF(Qbj-VY!b`+k&34S0S`Dlt?%e3Zou{Cy5kLv>dQW## zUze%<#rHMIYW@xn^Yc9CnJPkcG@Zxn{Kv5?my}A#2mL-Silgpc*SpB4R}K`^d{-hR zo|8VtH?T464eX~A>BO-@e3H=<0yv_+m8<*V;Xx9o2kxVz&%M3BW6#3xoXS&Sw6ZQF zG>>gVs`-Gb{T`~*0g+|(b&P9|REht0jqI_KRC+oSx>U(@ZhziE_I$(d9y zt&Dju|7ItMKHc)(>}%L91>3toC&8-S9X~wwf+_xRGMYQkHs$Q9-^U8|xsdP|1=%ys z&l##7W836jnqJ;rUHnYkERv@GZRguS}+ zJ4ZF(BEPNCxZ@<=Y3;ky`OTPYjfBL%JAW;WC+o?5nzuN0)mnkEb_8yNNMeGMh6cgV za!1#`O7-%3xrUf88T#5P-X8G>hWnh~S8n&D@+z$$PO*c$2}MjS>-5H% z2(=3>*1L`{G3`_dFgML;aB2_1LK& zSeHylBp`sp{N7-v?%V#5+UDl7ZNDVh)Abi^L*4kV#YuEfcF3-w)5=A&QILeoXWi$RZGRNC42%oxq@b6yoXEojpli%NkSEtnWlD+;%I zzZ4ZLeix>$ewnzRm>O@_5)?P{5ch^wM(#(qq(5+d5t0 zSoOL(SMyy^=k<5pEjsIe|7Dj!zH$GQl-I~BR?A`{z)p^E9kg%3R=r zkz$Amy=qy1(@)J!Q@VFcE{xgQSHB1oL0dv#|0ctiy2)3bfE$~1Vc5dtk?ea)X>97e zX&Mr@a^7r|q$pJ>cTLr|RdwYb42c%V+?pTDLSfy6==AlmW#c&*Gt<{pEoT3sAG)zY zpH?unmPL)lnrRE1$FV9R<(NK`nsm=pa?|&`_IfDHKB109>73{wz9a<$DHJl1xZl2C z03OJnV{(Ucob4c>c_EAv$5<(EgtE{(m0+Ju80y?C)O_ zWc%YpnZ*MbT>=4HC}^uT54Yh1qX^WsCzOd{ywF~lBKF20=kRG;!Nyw_`%GRL+(-~b z|FOrU+n#-E=sA&M>n%)~Y^pl%z|2q`&qJpQ-3)~=WwqmSk zmrzt8uTY11b8qKVp|@AVoMoJ~*DL$0Lf;GhJOtj&0a2B*8QLU5`qg`Ij>4h*Po!!^ zGRx+vL!{5x$+)=V8F|(R2YvSM7T2;YyD zZ^VA*9*NufS(o3q-hCmX{If1DKp4(_UUt0xj-EYK25 z9#fT8nK6M8QI%F$xDHZH4MHI_9A1L6Dt-T!ejnPORRrHi9N=y6m9H2s7#to;co-op zfHx;wXg{!mej~DnckRXZjnZU?Ut7&fE;&zc5zfTI~!+1p18Y#k#T|>-<5g19xkCY`ulTUCwR6UJD6ApTP7@c1OYo{Z17(s>x zj9+fv9w=F-8=hbJ`#PZ+871&Rnmi;{j`=Cn49Q;&3a`0b@2AdIHVvIOo6ilK+m+si<(jW2MO2IXHCwCfme2PNQQ2MiQLL@k z8+{I*cT0uW?9KDYJfzW2VROM2^*hFwTBks-0+rMo!G3n{|K9K`muwgMP&oFvKnN`C z3c{K3yxHdfe$9%<>)g|K)Ngft7uHEWDDrvTCuM4bHmQZ(sfWIhvW}(!e>6GPiMzUY zD6VAMG;^p1JYoe&rDd4h6G!a^5RT@QfhCKuDH#}Mr~FvAwpHh|2QhL%wDuf~>2*b| z^gQ(J3i@ir_g#mge%%X1X=A-oQq%mU(C{Hsw?wr#=s`OIT{c>0wQFnr zZz{<*5(83iUa9skv#{c$+Yd8_0$oe?q!z{$Z;2^qpP-W~;1a;)KJPb@VdD|ZQ-K6J zdIfqa)gnwGMze6AFa({Igy9#F>z#GSlNWUPA~~y-Xwb4(FxKI?mxCK2WA;jXhrpIZ zm&cX62In5ZTfKsqa0uOSnH-!eNAntn9~R)4Bns+PNsN`t(OJ@)tG)zO#qp*5=n9>w z+K#5PrxyJ2k2XVM*L>KFh@}sl!RQA z!=jhXfk9_pXSgRJFF+k8Ztt!7@U^o^-yh+gqD?j74Mi@WiF5xLb85@XHMB#U)%8;6 z??1X2nqI~touP8)@d*67x@r7^Bnv;16@$U5wBJcTzzpjtR4AvQaT2WP|Qi2)6@q*u#$mb%AY2(CL}^!|?@$ zu*x|^4B3u>mS4AU>b!r+OmLpNldy8A)cIMp?={9_~T=nEjS>R8ihwFxrm=IU9|P1!Wtc#SfGLU+V^A?_6n{HP_#s z)>$Ff+)}ficIxm6k38-8dg9Lmp+!dgQ=o5tSBR5J(jetSY{QV1c03Dkdq9;hqj&os z{$v{RfTSSr=jZ_54=xlxUEBoYAk~pAf^i`XLRF(IN;y^YNAG*bKBlzadK9$PDdpya zH;U@%_;2tYP78naeKI5Ss#=gND_t+MbEqTPRo*7qn+tL0ag090R=kwFW&BJ%W=!2@ zT^y@@65~+CGR&pAhhFcc?u)I|5t}avMa0g38?mx{@$)*#Q$Pu@A7Iz_5sMSZsUsfG zk~V`8h%cg%p6{`=!Q^8d(~;#Bne|+p%pA36Jt9r#=BqgV*@&=pVfEx#!EGocwJAVe zaYNE*A4*;KD`}7<;qESg9y!q6FoYala_~*p(&%Fci%9H4-w7{X332l7#^ zMz||_T6hZjuQiuT9A!vWoYGpf6^ZFN$;@Kg8pvmcPAUz}pVdty+GVnZlYsn;jqHYD zdFv?32@Zmoj_5@+rjO;7q&5>|1X(k`Jpk zA06;SHygi&!ADy~ic62vVucgxKbFV^r6UFjJqo^E`aYpfTb=Cv6f3VlSah_`JOz0< zp7I%|Gy9V22>T)|GnQ5e7oy}+=j_-W9SelGiiN@s>gr&ajY4pGz(;ZH%LnC5cOF9! zW}Ww@vSiHA1kQr&Idq3BTxr;yf9u3OL@212{8|p1EM)pp?2$4z$pv=8 z_5N2(e}5OCcybKHHf9|jSuK4zmsoM3u}K#=OY~<8Jl)in+2SUAtaZ`g`kO5~c=^^+ zQti1V2Qe$U2%u7WStD;~2uoAbO=0w$>FuB`SF2PAH$%myv&SqeXelaPfz6j`pLaPO zCnZFvkc+{RgM12hfCgl|el7h5#Hp=m7I*FLR(N8?Wwcaq*g`)}XMpofbN_u}zvQ>H?Kla1gV1l zho73tqGIIFO61CxWKcivIO)1udvJol7N#x&i(qx9=0LMcqXlvlqcF$P$ z)J&!kR757j!evc9bjZqiS1ZiS2H9CKD;EL=g$p&Edg!@SLqc4!A|8RVEpdL>jk6bfv@Ve0au z-ZzK=WEmpJ67J%k&b4)ik$>k!r^MR#a=X{y*T}scA-NCQx1y09yuf*@QR1QEqIfUjT0rogKaT@Rn7nRPmG!2};lAoTVSR8!Q$70sd z85Pq^BF=-cCERC!moCMgetDbIr%M@z$Pmd`#-Y_N2LuM;D}t!%N!q(k;^0Yf7JHLR z3*ON#1Qo`m#*-n71ZNM~zoSl!Am^58Ls0i6HO?I*QY*!1=hNR+xlCI%-_|z7+&D5LYi;c^<`!ldP9=nVI z`?JF$ee7<%N$XUZ*GRZlai_TW(~GfCLNN|=AB2S&cc9{r)#?RnOdidMK%wmSj`mi< z&IWTr@?jqX^ELv%rM|&wDL@O4y&O3WasQzu)V)3e~cgfUCeC zoMbU4<9=%K4dgb#eQNoDDCwJPsU8q7nERfz218f|LfqByHoozlJf_T`D?JfJzg?Iy=sW97#+e*9GMyX zY>GI;KJip&TVX$w95>5-6|eBqRl;2pN0&ppduL-M2x^?+33Jt3?r5>uPo&}5#xgtf zdX(tbm^peKXci}{o7zE)CGJu1p$Q(-LyNK3vq-Za1t2TJivoiGodt0q!Uug2%~;)- zKP@Z91JQcE_xpb~ z2&s-%DCX@wO6(X|+J2cMD9u^x8m| z;aF9hPQa(6R_f-4z&QY+m)|B)h|zq zW}r-KO;t{#D$T(b)M)~!pjWYnwEUE|?riMb`6KLU!;x1mY6I{1{T4RhT3j_+%-@pq za#N}dGnYUVYkOtk8q9o(=-0yE+MP5CU}~~mR#M&2yKA7R^Cs&0@jH7+SO5hbU(yDe z*pkc|liZx_8j}SK{Mz6&_Gpr#fF&plm?0G`!xn6O#xVh>wgg9Qt0NU0N6Bh?oJLw6 zbHTi2OCI%8q~y$Y&d8;$6E10|a`O2~&+C^QvmXPL>{%lHQwEF{4iE-Zp0}cFj;hU) z^T_Mrv3fOaH+2#Q^8zPf5Y~1;J+6g5IEopuD38EgJWUb&2Q55Re|Ar>p8J~$jo5N% z8K)M5C>0E3rwtGMA4RQp^1PU98F}@>7~@|C+1Fx=jPAAO4X7> zc%V&a6&EOnyE7<2BoB=r1`OQdqmnQn1`R)^1U+K;PvN5TxFt_czVImqCpP>Xxv5LI zG>}V&N_MWduh5pRu)-9;SAAWDpf7`@IPKUh)}7FluV4gKYSc{q73xMxx*tw*0Q2LM-i*r<9AU`pZhOCFvD0*q&6P)_F_h)8mJNQ zwJ6Te87Yx=S(BwSTOJ)y3U?b;^_JUHfsslb_{UYa;J||VBbkuMAo}w`5eiNHg09y{S&E& zX$ek_&Tf~k3q{A+dGIoZ7x6QXfXT|&^|(|&|2Rq{`BsP}3TU?vo;_>ZaZvLnXA~TU zYTK8bpw>7{2=>MjGv&h%c6Y{8Gqk;(*I|HdWluWIw~N`C#69Z}xOFP5Y7Jgq9kn9{ zDM+{^VF8B3S=rM?)rXK?3RJ^q(8oQxawT!f*|2AC9B zI~QPy!nM>}Id)7L*Bwsva_iv8ztGg<{cJSeYsAJbN}QsgX;wtYIPf}FME?HR*s&)@ zeR&B0JG2wp;JJd~zg4W4)nXEr?9J?a6UB{%F2`y|2`{9Gln}Q;0T3nRmo-TI&cdT? ztLpNb3nAt$h4V=`~TqRmF|uS3`I3yy-RuZJCEc48?yK ztEWcFqz@&CL_MqfXu$uC1wkgZLnEo@ zX1T*+#HD?oK@g(>Kz3^dnd3D_&W9*2{boh4Z~!e2uMEI6#sqd5e{9a+SKrmFB?p=b zMbLBVmF>tPZ=jIQ++exOO*=t5s&9?6-Of+G&jkBByq<)5FD|DWF!`I3N4OP3&jx>| z)5AOi+*70jI9OP&U7v=LIK%++s^e-@QN3`o9A!c9m@Cp2)A2$Mx<*4HG!``?&Bsff zN)Mv{NZ0gf{f;6^L(*EoTLE;Rf!zW6W8&H2c9w3}oiWJ>cd+eR0?om~-tKX|#Dvs$ zA3uTM??38}Y&X+)m?WYKH;ZY+2kv3eJH`9-sEz0AB+~TCWyA((Tshbt_#D}lwCK_P z4v%69u*HX7`6q0GKgjmVNJa@!AAlfQaw_n%?G3!Pb0#I^l9=XIvza{nBrZgIXe#2$2JnV72b)qVOE4 zq>0~K=UINIV*rodRaNiaJ}y#fK*Utu?Hjs6*Li;;N>f#4#HUMoT)&2sPCQWCMdvr1 zzfAkt=abKK&X1Q)+QSU={I`+f*0TLYVAZkJpF*QS&KoSfONu;}sL(!1@;{zn((roF z=(bsHSj%Lwws3MW+gn>VXN9c@PNV?&XAW&!0M-u^0^}f7^!4>x)>G;--oTG%#@nU~ z|G7WfcJ_lVhRkWo$=CQFIVfvaOseb6h#kB$+aD%rt#WYW`c#rw0ZO~1$e$vMO7;d@ zCl*LWSm7Voz!wq0{4W=x?Q4kfu(#I1pxm=i`$=I1cm2m$Y8@5%r!udg3YwXJLH4c! z4z(zs>zG8_3!2o=wv2yOO8erU2=$afbQz7wSp~jQdqgHOwV&i1( zzT0S3$vK6wna$}k8>`}>Ui^ABL+_2v-r9s$&lkb<+w^hjko229xC~X)ZliI&y0@c+ zwv{HwNr;MagAbuFV(W(?Tu^>$aBVmu*=L}rcOamVOH1w)&`s3;=|D`7s*eVr_Pao7 zuy+v+QB!plOl$4%y6Oju-zOWIuRBkFd@g^?ZI#<7{uuzreRiu`%Z9uQ0-;(~jM>y$ zzpqVP>$Wx|lCe!gFwsFPPWf!yMnubPCU{^`UvH_%k3^OXX${wp`!r!LD55v5rge@q zthStGmAH?i`Hx3qSsDR6dVMoYq|JhNd8Q6Ulb$J9l7(7+kmzqbF`;I4mZZS;3$6v7 zBrDxv2kZ<8WyqKO0hhu?#@Xlqi_QHRWjsgySJZ*d}u zx1Y2<&LI6T(GX9^d1m7c%O?YG!Kl(L$)c&iI`gpr)!nC*4IJ6-x4guI`gjNtTtyL(_etO`5c{?j+pM?ccZ5aoS$N8 z{?hlPw={%JrD11L#+NGp6!|zxYef(V!`C9c9cBVx@Jz5Q6L!>_PQN?^fp{hjT~tfN zSZ#BpWN$yYXCA1@ zBPF!9fqER2J|*d0_YpCGpM@AvEs9D-b{LJ&gH57;T`gr3BQSr_XcX<{?8rRfs9~<( znsmhFY~JK^w?%~aa`>cA#<&70_g~!>CGGhm_KiHHt*X@W?#**OMWZ0)9L3@I{y4@R zzn+g<W^kd2D!QR+5N(zsPa(`==(Uf`uGD-LrN=GAulxc8mA!u2 zaJh1l&o_o%=vSy)B@2vb@agW=1C9b56l*1a#ePo&CS$~PUhA%m<)61qKZr>54cH>9 z-i{dw!tq%0ZA&B|6&CRSt!yM5lp5=4LTU<2WYpZbKFOAsyKma7Kk9GK!R{k{tl+pS~6d-;?VeX;Xs({icm|<2D$Y&bxp|+BuTv zw~zpinhYPlFYLjAH%H2Lk-tYzuQ6$nB9AS$cC^i@>pMb+HSo_>7X?ajNYd4*BQoL$ zQc=U^dcs=t;1#uy5Rm1n4CsS166jPWN+nRWdU$7ZF(t^FU^(8f0E`+-x&;x&*~Q|u zZ-|-)-FS*&02BcjZvJSbtrCBg;#7>cAOJ*xUXMMKU|bxjBuS4;OWz6dS!<7sP%Nm0 z4!{*9e4ssEGTXC#!E+4zAQhBQ=6JrG0$16dHIj6yqG<^k^w;h}>7mu=x;7tge#w@n zK+eQ{oil1uTeh!P|4fgP5Xy{08lUYL#-rEr*v^_&U{&qo)u~|lmJC-n7gM7v3 zS8n!iZ*NKpOa?`!48;X~XthAa?OQ4U=3RyWXeFpyB9+R9@*WHadK7b6BPa|GmCoio zt*lcDfe)>nsSS7ft`JWyT8Il(!^esc-(J}&3A1jzyVF8Upsp1*9CtT1POZm zHctrO>oSJAICIfCXILZ=((mx((9>iNW_X;TL1E%}Zf-CbeWYc7Mv-WI=MWrPu;Zzl z;$Mx3B;L=#i6kyQH}J8hLKCtlDy>DqRwp=WU?XP2(jIwU4cq9R60knW>ocrsX22^& zb7#X7M-ic_V|H_FlNcNn7B;M!Y%H--Sr-!>9k5OGoq|OwNyUECP>Ng^V$mf$PCgB%qLGNa)lj)Q58Bp*fVP6-!})u$U1AvD9(xT^Yl( zsM7qRwfRPvn3r0I#?c6K>xw?)iOqf5iDottcw9>?y`=-&49-C+h#D@d@y5f`DTcSf zl!kLU9@!4*3gPVm=lA506W;xauYL~~1pA_WrUMlG9jvH!LNlX*WEo~@JaY%ckCD=} z;?Nfzn>)#yo3RbHO&A~gJuIHNQ@6Gwt7U0X80K7v**@B(_Z=yRPe$O^)yu}$Dgr^U zw^L|4D|eHq+YUb(;R;lO)6lUOwM)WB>1;nfA0`FlVk){Gb9KLW*38X~DOht&Sa7T| z=%z5PBtQ?jc8s_NGK!hlz(JXW#fcC({No`|nuO<#tqcz4*@*{(4-a@GIa(Qlxmu2i z)?^r4ZBCE>wdLtq++Oln{`yg>%}K5c!%TkS3j}hUR-aUt>u!1Zb#J|IY%PqS_lhDj zg_g1X9%M1C%*zFX7BKsbVM=)6?to4vf`^FaV$7nzcEe%OLZY zi5a=dm|$pE$U2{dcHmUIdjO>#_O)E^mOr(#0?$<*eMr`Fb->klhBWdI*|Ky1Q#@H< zE*ueoJ8c4$bUFiNLeh<*8%`q9v?wIWNL*120KE^$st}_3Hwrv~?kC-Gn#PHF5>-hp z;%4ev?+UEe@oaA)9+ic{HgYH_T5ug{+IT>_6>c~Ed~Xu!xN)#IyaH~$2c<*-;+#dc z)$6hIk83dN#meoYH7-nkaOp=v5IK4t)&mP$g6P;0ycd-H4|_GSCW_I$!X_q(>@Ano zNuy=Vz4~ay$n_y_Z8nYWh{qahJy&G(VQFr>D9eHhkq{!P(qYkLOq+Omf7!oUz;Ff? zG1CMr7$GvOG_n|2LdbBlC=Pl{2@;1rkpdY(M<}M^k!?zU5thFs(Nbut|CSrODjZpc z^6-a$h60?j3mft4A)Nx~C?HfGNZ-Hp3T7vlzz>WN?TetTGhnrAONE-qSssr8E){EI9BWW#E%bWS*q~p#YO-3j35rGq<=!2#TGX(yVX0@uKZ2**K51jvZ)KJuT{s0+b=qf9Fu9}~uizuOxte#L2D<{h$ z@PYSVX{+A`&z}L~_kHvXE$*=Ko`QKW7uc7>XdW|?)Q>y-?lRA@U<^ZUmJL2slnQ@B zRnv~wAfNzGO=*}W!X~OXFK#N144&&O%f6NHAY}8W(rj#SLLT8|;gXJD$QN?v_#X!| z;c;b;GBs^Nuth!$vS>Vnr~mc3O-B$#5K zJ4z%z#0_60F`?RGdea}6+W;8jT@1-t9io3qEQbdx-&|L8S>NzY&Pf1F?MT%nFnGGi zMGR4dVIqn8EaHxr8F%b9tMh+Fwos%D-9rgAYs{~D!C|~!EUOw7J)6gRd@=^<6EkEP z^4H|+4b4TA6lGr+gmLZci?}*~UG7IJ*jLGGtxvlL*(WXmm8M$zj$b%w>ozK-Z4PaB z92`c8Y1Y!gDexHdBAD<(5$tR%rH;89aNBTykf0xPR^zr1IAyL+7EKT~F!F?7RDWw6+zjbnds{qVwrD74kHBSbQ)oIILXeE!rq;y! zIc6jan9w;VbOSY3?f$J;%>8k$-%oYolDXnBS2X5Jw;V8ulTI%y;Ia->6 zs?p!cjX5>6s-E9a=7UT*q^RouBuQ|=P0_q;b9th-qqXbQ+i~8?lNLOv-D^vBH~r3V za%xa-kNt%Dk}F6FfX+A2(P?MjMh@koik!0ua`V*~RMY9$^%%*i2?0bwsuL3umEjfx zQ|0)~lia08dew3{LNMQifskv>ZVc=G{}}ReNK)m4QYAj`*wRid$Js>#`HBHwdJ39x zF^d+ZU{ts$l&jng6rPj9j0_ee?$0b9$1llvy-N|<1I^)I5D^7yYz7g48ilT~0|8Y( zA9{VBy(aG&uvvmTlT<%4K;_lIKln`RzSe>Nexd*-;P`Dz)y!gb++`JUgG1#4zgx=ZR<344A7(a_K_!4aKG>d)vWvyP1_AcE6Su zKI&5m9nvw4Pum*LW1ze%QZN{MYTaMF@II|yh(s{T%8o9mGgw5A*L9Z=P}YV+NYg0k z^u)D;kwE2DJC9>s#26ab{XQc||In)CFV(3hFMAG36+Io@rVtZripLZ$@oqo_-IMJR z8dVRddg4^QB?u1w04{^K;jtUI1?ftM`XcJ77ST{XQ3k4`WM8Z;)p|I8tLc~>RVMOH zc6a^#9X`5NnZH0kgb^N79VfdVNBo1YNv}Ix*dLYg_=0*+3D zXOsfc?1faHB78g=POD%iLhONLZLcTS_T8SK^YQsE8HhM8-MswMrr8I;~@= z*=fqD1^PLE_^@eM++xw@Jhauvk5I~htOAFhjFmY4H@5@34IzrealhzlGCVFJs-|zMH zgy3G245$_&vEmj@ksbW>&+t?a{G_a-C$^rSe*z;8A2^BS-?At~M~r+w9}bK4ebJsc zwIp9BA^Db$5p*n1anf?lPTI!8s!g9cnnyYS-s82(=P8NsX9V#`8&=i`t~3|tmC~Q> zV2Wyf63iTLqCS1>;6Q;zH0?StKZ!KKq9smVVipk#9>c7?0}7RhBf-K6lJ9%U>TRo7 zdZ|r1(Z-Ppn3$PAhT0|AsYXyKy1|VrCTZWqzh9omAbDUsbu20|AD?4u3~3!MawOY# zI*l^Qar_mz-|9=~!SPP))3Ipc%R3_6*m6(d>hkZE;zss9CnA3<;_IPRo^Rx?BixV( zvy&?eC7W0Vu?nIR(DD8zS*EUyvipms@Z8s6Hk#|SD9WsWnbFLMC*4b=-7L6}_$RrD zXyJwY7Z@|3NBI3{UgH`51%L@#rZ%{J_uZT#0Rfso!Xn|SU(vx;Pm`vXeiEaP11&+e z9*LdSla@7HHh_)yOJ@ZldAt8kh{N3>t+dO)=-54P0^Kb1> ztCmh;M^eVQX{SuL!PA#9xp#bljfpE;jK5C0fe{PtC#`QK(_oJ>cgA8k&ivYABdHvsrl3!vDPk-6r+uv1Z5vMg3c<4i0upztLa3QDz5cMTsHnBk zML85Te>-00to9lX8UF^(jTz<%h^T{8mF>pU1! z!Rv9(;-v&cziadOmu~LRLjL5978wc#AU-!P+~1LD3;RM}mo$fxhds`6XB4x{4!VmI zdV;=1vyC>Uk~vZHvZ!oFEW)5%3ir5kDR*Sm9N6psQti0>YcQjE*$T93PAl?x6{8y$RiR z`Gxg&-j&>ZT7Y-|87JM(mzd5zqc-%y%wn9hEP(mvrsi+1^~nk^w>h+!>14dTQ%h-$ z*$&Trv^M6RpNftTs8MK>?c?XjzlbQ+P%z0Y8_xWGyVM+z0_zDjRpMvi@H_PR{cv011cac*Mg((bdk%u@!q~)3di}8S~azBXwR$vjF7K>Sg3iGubw<8 zi9X?Go+f^kf-gaWV{-H|kz)0S+Bam@MUN;8TSClqaHXgxOIr)EM<|*Us~am6xfKG_ zbhf&@NSiJ(614p0?%1KBhfT~p&o49DO8Jp4Kk`DFVHe6iH&5s<7`VO3u z_ikD|rUV~H8rV`daVvl7Uo+m7gf!U%H8`2=)QhEG1{DhaaxPJcJJyEy z{X-7^;{v1weQ~cm{71nlL_lVA^D@Z$XolEG+m);n&EfS`s3#>xkpxPp8CP65h$Aq+ z(Z)`cFZ(bAm#A3HBPk|^7!yfpcIDY1$M3^LN5{@|u2`}Aj>vPtDp}LeCuErDRbA+9 zCl_StEclZP@$=)#;iSl6l(Ol12Vq-@(w*|jjcBeuZA597e{pJw_zC7&EeAO@>NccX z(7$@@Bp&_pQjKbAQln+4qGW!NYsTZL)pEiT7ArZEg}$ zEc(ZhoxODw0Nd3UL&&YJetY=XDeU{kBmB0L`gB*4TioiQbe@j%Ma2_bZJ2im{e57%t}GUREga$YX(9u z>A5$tkA9gZt$!-3@3(se|1`ybKfvkgBHldd4Ikbtlj*h2UcwhL+c1m*(;ElaKhdAh*Gj6LH{U+4Oqjs_)}H z^6|H@H{h~$7)59Tw{r@1rc}1W*1x}VICX(Wn2Sq6QPCs7rQT$4{nf5{1ALq-4QA(B zJ!#DR2eLU3m~`cHHPv!E37SLDS=!kn4nzKyM3S_ywEHlCLP8}|4>Hj zey0i`3e~j>fF<%@GRS=G6c5|}c(wHdS|RwpsPofsii<;f-=P|8$76o>T<*Qr?f=v2 z*Vp|2K(<&-&*Zqi z+E;>$&V`zCN6Po+7p5pf6%>1eP{rt=iJyN9+5*QIr?-T;|Dq!GxGb#*U0*bdw;~CD z)Cpd2usbz1$yImU(E2{@2Wh3WGE}?uheq;xaHuTe!I5!gNzl1aNB};{Xp{G zJbA2$8`X>6y3$mx**(YUNJ@}4c(G^NyrHnYO)1e$v31>a&1BpjCJuXWU2b)@)443K zFxr!HKcxi_hM@XRa~({0wBbMmQ^h+yYgoX;1R){&q<9Gkm9rpvv*tvULeqZ<+Mky@|AtLCF0Q%B=~aU*oT`t;IeZG z=ii&@FQU^hqQEX8eiD~4Mpu&a`7mPA)M(inOZ+AMM`I$-V-z_`ev^;P+(7zV|0m_0 z$=sec%Pmh6h#Zf@IX&O!Wb?OYkjn3h>K7hVK`dk1=FjJ&oeIMczYp`{Qyo4)xk{j) z?$b)e6sGnoTZJaGpd2_w;x!0$qR=mgj;Q5+v9&P1s9q&A6XbGcE%5Tsk6fE5A2qE1 zPqSanmrTVv`E#LMqYt}efC@s>pmVt1M#%AbcwKkj%eJTMb@0sHs`j$|{E3t(hJvKx8vcHt)!E+hE3C=6&zgyeKGX)At zKbBiqdlxi^zk;*7Qm5`8W`6!xFCx8nF;}2hT+bGJue`y#OnaeG#b6zFrMH*&{ii^S zlV9bOUn`|Ec<#b?PgPou2Iu1ZPlM6MIBj*L2wZOlr}bw2)ZOd*`Fd-l_Xj?QA`||} znr8p!n-|LmK0(i-rL%0Q2Os5AGiLLgWF1}M(M;Mfk^B)Gj9yeae z2yb_yEw?C7XTo8dIjuH)wUV1lu!f)Co(96UyFY4d(X;+``COm-@IPNSHh;+dsuu%E zJsHMZ)WWEusM0X_DlZu;Vleq2Eu-doBe;ZWW?Uk->a(u zf&9`W1-+5{+YN?0E(PxhA#5jGE}e*W-G?~7H;EOz4%b{@MRYV;5b@F~=< z!@PE4AH>e!qTrtfU$@Eqz%!Zk1^SMhSe7-F)ycwDcFaTYceZ@I?NDvQ7l6dpza}yg zgT)XhUZQdPhCc{C8qu?|YY)vyCjL+-6VLYw`lRjD>}_P&?)$FHkyM1Y?p*2TX8U(cmr+r>Htk*4 zDR^HYcEs(!Kc7yNq2D_jTVRDo_4(bNRM}m;=$<5J4#T^+I=H;_ zlXNSzv<~`aSNluaop$GDm=lEvYV2fy772!d+dc$qk zfQS);|H!7s5gD2%l13O@3om!b`+ph5|ECiBzb)tgyBYoe2@!ECbDi>&)SSy&od(OC zQ%%m}6z8NNkXzAN>iSfP7V9W<8~4P+wW3T6Uqw=`df%`+y!DWx-EL<3kwDaKucUiPNe!vV3!o-!UheJ79|q=zy(qD9q`*)J z>JJy!9K2CwjYzGjL^^)9=>SW-5D>IxKiEKCQrRC^A!TlJ5c|pAbmAO!!jPn!&6Y)=f zN|N>M3BN*Tezi1iTTs>+Rz(0f-BaGZ1#a`}6CAy|tU50T{gCi+<#Z5U6 zDSY*&wCnLy9ZVA+yl6Xvf??Y2zJ&0^Q8;;Q-U-;@$ItW+HZeCq{5iH8Pw@uwl48q9 zHuFZj+zl2a0+g^UrQt;Ol=su%+>Li=j7x-+{-7VD>NF{`C3A3YY5plCx9-f?J3V{T zEcBFp@c{Kia;;Dw@BF&t%SpQk>_{1o2=NLwAt71)j+D`J3eIq<4y+qk2><<_3g)@x z`IscC5IHaRRc8sH=XzpcqhtHGrNdLrj3DcQ++51qM}3go&$$C<{v!EE>Lc@ zD7?IV5={MCzaad0r0Q5?c{XMGD%ii%u?nRZF`*X}6@^S*`hG3rVbhzD(Q|NPtW8y+ zvo;cqZ1U9iw4tO%4(Z-0L^qp9sXBJ;9^=2H$gY1_oB-#w#s<&5vpIQ zWOM~-r~H%+>kp_tYY=*`tZKAgVzM?>?$@P!=658(RWDF9A5)kk_Hm-aso*8!@u916D|ApYnY5H|U62p=2ph zCt~7R6&TA=B9d05sqNl+0*DEVB1ET;3M~-69+ku|l!p0Dby~{;=Sf)DASot9y&{&p znvth=n=awNm{?cGiqwo-(%)0oE5W5)#3EzJ)dOvNuT>l#Ffc13ZpxNAOsfD?_V*ta z?XuWqqbLKy81{sX?G&nU(jGMAwO_Zo%%(#Kb;HK4$=i{I09#mqkc6212ZMCDBl}~3 zK8aJMtWX%S{v#(gJ}#w}CY-P@cq%q}Xpl)=)EF9EzoerTL|Hq!j9KGvho@^V{iB1C z9J!J@g)h5RzD}d>#6iU{wbb|HHg|2)K2| zeg^I^*=6uedEAE*%=h&^V>%EqW})q3X+v(rJYpJXa(l@vGJep@CO$C3ayRI>-r}`` z{NQq?(A%U=3?XFe-?Od#^J73KQQ4eYGbWze=?+;Ga8Wje9Zd*PUZbC2iq9p7*G=vbk zFciZ2FAAv8w-((vcjvFZ7rbzZN(&C1(O(T;UQ*xbuxIh&xNYTWMv~f_v z`pbV*{9pl8#>B=lqrK*%`-a+nVP-=^VHmTO5Wy7Zg1`Ed>pCRuwNs!<%gW+?e0&z2 zrv#~Sy}(LHaU#R3bene{Ume>w@^z}p+sjoL)RFr0v>5*CF%3t4!4{5>OZ8R{{0idy kzdEMR)?em2Z$81*!$z#SN=