WJG (16/09/25) The latest pre-release versions of Gnocl have support for Cairo function calls through the use of "actions". Adding this functionality allows direct drawing and manipulation of the pixbuf and drawing area objects without the need to modify the C source library modules.
For those applications requiring the use of a fully features canvas object, bindings exist for the Goocanvas widget which, in turn, is built upon Cairo.
The follow sample source script demonstrates how the Cairo "code samples" can be implemented using the pixbuf actions sub-command. Each line of the drawing script is parsed and executes the corresponding Cairo API call.
# !/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"
package require Gnocl
# set colours and draw primitives
proc test-draw-1 { im pb } {
set xc 250 ; set yc 250
set rad [expr 500/4]
set yc [expr $rad+$rad/6]
set frac 0
for {set frac 0} {$frac <= 360} {incr frac 15} {
set actions "
rectangle {0 0 500 500}
set_source_rgba {0 0 0 1}
fill
set_source_rgba { 1 1 1 1 }
set_line_width 3
arc { $xc $yc $rad 360 }
close_path
fill
set_source_rgb { 1 0 0 1}
arc { $xc $yc [expr $rad-4] -90 [expr -90+$frac] }
line_to {$xc $yc}
fill
set_source_rgba { 1 1 1 1}
arc { $xc $yc $rad 0 360 }
stroke
select_font_face { Sans }
set_font_size {25}
move_to { $xc [expr $yc+$rad+40]}
show_text $frac
stroke
set_source_rgba { 0 1 1 1}
set_line_width 5
move_to {100 200}
curve_to {200 100 300 300 400 100}
stroke
surface_destroy
"
$pb actions $actions
$im configure -image %?$pb
gnocl::update
$pb save myfile-$frac.png
}
}
# load images
proc test-draw-2 { im pb } {
set actions "
# {some comment}
image_surface_create_from_png romedalen.png
set_source_surface { 10 10}
paint
# {another comment}
image_surface_create_from_png hatch.png
rotate 45
set_source_surface { 10 10}
paint
"
$pb actions $actions
$im configure -image %?$pb
gnocl::update
#$pb save myfile-$frac.png
}
# draw text
proc test-draw-3 {im pb} {
set actions "
# {create text}
set_source_rgba { 1 1 1 1 }
select_font_face { Sans normal bold}
set_font_size {25}
move_to {20 100}
show_text Hidehi
stroke
# {create text}
move_to {20 150}
show_text Hodeho
stroke
# {create text}
set_font_size {90}
move_to {20 250}
text_path Campers!
set_source_rgba {0 1 1 1}
fill_preserve
set_source_rgba {1 0 1 1}
set_line_width 3
stroke
surface_destroy
"
$pb actions $actions
$im configure -image %?$pb
gnocl::update
}
# CAIRO SAMPLES ~~~~~~~~~~~~~~~~~~~~~~~~~
proc action:arc {} {
set xc 128.0
set yc 128.0
set radius 100.0
set angle1 45.0
set angle2 180.0
set actions "
set_source_rgba {0 0 0 1}
set_line_width {10.0}
arc {$xc $yc $radius $angle1 $angle2}
stroke
# {draw helping lines}
set_source_rgba {1 0.2 0.2 0.6}
set_line_width {6.0}
arc {$xc $yc 10.0 0 360}
fill
arc {$xc $yc $radius $angle1 $angle1}
line_to {$xc $yc}
arc {$xc $yc $radius $angle2 $angle2}
line_to {$xc $yc}
stroke
"
return $actions
}
proc action:arc_negative {} {
set xc 128.0
set yc 128.0
set radius 100.0
set angle1 45.0
set angle2 180.0
set actions "
set_source_rgba {0 0 0 1}
set_line_width {10.0}
arc_negative {$xc $yc $radius $angle1 $angle2}
stroke
# {draw helping lines}
set_source_rgba {1 0.2 0.2 0.6}
set_line_width {6.0}
arc {$xc $yc 10.0 0 360}
fill
arc {$xc $yc $radius $angle1 $angle1}
line_to {$xc $yc}
arc {$xc $yc $radius $angle2 $angle2}
line_to {$xc $yc}
stroke
"
return $actions
}
proc action:dash {} {
set dashes {50.0 10.0 10.0 10.0}
set ndash [llength $dashes]
set offset -100.0
set actions "
set_source_rgb {0 0 0}
set_dash {{$dashes} $ndash $offset}
set_line_width 10.0
move_to {128.0 25.6}
line_to {230.4 230.4}
rel_line_to {-102.4 0.0}
curve_to {51.2 230.4 51.2 128.0 128.0 128.0}
stroke
"
return $actions
}
proc action:fill_stroke_2 {} {
set actions "
move_to {128.0 25.6}
line_to {230.4 230.4}
rel_line_to {-102.4 0.0}
curve_to {51.2 230.4 51.2 128.0 128.0 128.0}
close_path
move_to {64.0 25.6}
rel_line_to {51.2 51.2}
rel_line_to {-51.2 51.2}
rel_line_to {-51.2 -51.2}
close_path
set_line_width {10.0}
set_source_rgb {0 0 1}
fill_preserve
set_source_rgb {0 0 0}
stroke
"
return $actions
}
proc action:clip {} {
set actions "
set_source_rgb {0 0 0 1}
arc {128.0 128.0 76.8 0 360}
clip
new_path
# {current path is not consumed by clip}
rectangle {0 0 256 256}
fill
set_source_rgb {0 1 0 1}
move_to {0 0}
line_to {256 256}
move_to {256 0}
line_to {0 256}
set_line_width {10.0}
stroke
"
return $actions
}
proc action:clip_image {} {
set actions "
arc {128.0 128.0 76.8 0 360}
clip
new_path
# {path not consumed by clip}
image_surface_create_from_png romedalen.png
scale {0.5 0.5}
set_source_surface {0 0}
paint
surface_destroy
"
return $actions
}
proc action:curve_rectangle {} {
# a custom shape that could be wrapped in a function
set x0 25.6 ;# parameters like rectangle
set y0 25.6
set rect_width 204.8
set rect_height 204.8
set radius 102.4 ;# and an approximate curvature radius
set x1 [expr $x0+$rect_width]
set y1 [expr $y0+$rect_height]
# if (!rect_width || !rect_height) return;
set actions ""
if { [expr $rect_width/2] < $radius } {
if { [expr $rect_height/$2]<$radius } {
lappend actions \
"move_to {$x0 [expr ($y0+$y1)/2]}
curve_to {$x0 $y0 $x0 $y0 [expr ($x0+$x1)/2] $y0}
curve_to {$x1 $y0 $x1 $y0 $x1 [expr ($y0+$y1)/2]}
curve_to {$x1 $y1 $x1 $y1 [expr ($x1+$x0)/2 $y1)}
curve_to {$x0 $y1 $x0 $y1 $x0 [expr ($y0+$y1)/2)]}"
} else {
lappend actions \
"move_to {$x0 [expr $y0+$radius]}
curve_to {$x0 $y0 $x0 $y0 [expr ($x0 + $x1)/2] $y0}
curve_to {$x1 $y0 $x1 $y0 $x1 $y0+$radius}
line_to {$x1 [expr $y1-$radius]}
curve_to {$x1 $y1 $x1 $y1 [expr ($x1+$x0)/2] $y1}
curve_to {$x0 $y1 $x0 $y1 $x0 [expr $y1-$radius}"
}
} else {
if {[expr $rect_height/2]<$radius} {
lappend actions \
"move_to {$x0 [expr ($y0+$y1)/2]}
curve_to {$x0 $y0 $x0 $y0 [expr $x0+$radius] $y0}
line_to {[expr $x1-$radius] $y0}
curve_to {$x1 $y0 $x1 $y0 $x1 [expr ($y0+$y1)/2]}
curve_to {$x1 $y1 $x1 $y1 [expr $x1-$radius] $y1}
line_to {[expr $x0+$radius] $y1}
curve_to {$x0 $y1 $x0 $y1 $x0 [expr ($y0+$y1)/2]}"
} else {
lappend actions \
"move_to {$x0 [expr $y0+$radius]}
curve_to {$x0 $y0 $x0 $y0 [expr $x0+$radius] $y0}
line_to {[expr $x1-$radius] $y0}
curve_to {$x1 $y0 $x1 $y0 $x1 [expr $y0+$radius]}
line_to {$x1 [expr $y1-$radius]}
curve_to {$x1 $y1 $x1 $y1 [expr $x1-$radius] $y1}
line_to {[expr $x0+$radius] $y1}
curve_to {$x0 $y1 $x0 $y1 $x0 [expr $y1-$radius]}"
}
}
lappend actions "
close_path
set_source_rgb {0.5 0.5 1}
fill_preserve
set_source_rgba {0.5 0 0 0.5}
set_line_width {10.0}
stroke"
return [join $actions]
}
proc action:image_pattern {} {
puts [expr (512/256.0)*5.0]~[expr (384/256.0)*5.0]
set actions "
# {load image, create surface and set opts}
image_surface_create_from_png romedalen.png
pattern_create_for_surface
pattern_set_extend repeat
# {this will affect the rectangle}
translate {128 128}
rotate 45
scale {[expr 1 / sqrt (2)] [expr 1 / sqrt (2)]}
translate {-128 -128}
# {
scale x/y to squeeze-fit 5x5 array based upon pattern created from image
ensure values passed to expr are floats ensure non-integer calculations
eg: [expr (512.0/256.0)*5.0] [expr (384.0/256.0)*5.0]
}
matrix_init_scale {[expr (512.0/256.0)*5.0] [expr (384.0/256.0)*5.0]}
pattern_set_matrix
set_source pattern
# {create and render the rectangle}
rectangle {0 0 256 256}
fill
# {tidy up}
pattern_destroy
surface_destroy
"
}
proc action:rounded_rectangle {} {
# a custom shape that could be wrapped in a function
set x 25.6 ;#parameters like rectangle
set y 25.6
set width 204.8
set height 204.8
set aspect 1.0 ;# aspect ratio */
set corner_radius [expr $height/10.0]
set radius [expr $corner_radius/$aspect]
set actions "
new_sub_path
arc {[expr $x + $width - $radius] [expr $y + $radius] $radius -90 0}
arc {[expr $x + $width - $radius] [expr $y + $height - $radius] $radius 0 90}
arc {[expr $x + $radius] [expr $y + $height - $radius] $radius 90 180}
arc {[expr $x + $radius] [expr $y + $radius] $radius 180 270}
close_path
set_source_rgb {0.5 0.5 1}
fill_preserve
set_source_rgba {0.5 0 0 0.5}
set_line_width {10.0}
stroke
"
return $actions
}
proc action:curve_to {} {
set x 25.6 ; set y 128.0
set x1 102.4 ; set y1 230.4
set x2 153.6 ; set y2 25.6
set x3 230.4 ; set y3 128.0
set actions "
set_source_rgba {0 0 0 1}
move_to {$x $y}
curve_to {$x1 $y1 $x2 $y2 $x3 $y3}
set_line_width 10.0
stroke
set_source_rgba {1 0.2 0.2 0.6}
set_line_width 6.0
move_to {$x $y}
line_to {$x1 $y1}
move_to {$x2 $y2}
line_to {$x3 $y3}
stroke
"
return $actions
}
proc action:fill_style {} {
set actions "
set_line_width 6
rectangle {12 12 232 70}
new_sub_path
arc {64 64 40 0 360}
new_sub_path
arc_negative {192 64 40 0 -360}
set_fill_rule even-odd
set_source_rgb {0 0.7 0}
fill_preserve
set_source_rgb {0 0 0}
stroke
translate {0 128}
rectangle {12 12 232 70}
new_sub_path
arc {64 64 40 0 360}
new_sub_path
arc_negative {192 64 40 0 -360}
set_fill_rule winding
set_source_rgb {0 0 0.9}
fill_preserve
set_source_rgb {0 0 0}
stroke
"
return $actions
}
proc action:gradient {} {
set actions "
pattern_create_linear {0.0 0.0 0.0 256.0}
pattern_add_color_stop_rgba {1 0 0 0 1}
pattern_add_color_stop_rgba {0 1 1 1 1}
rectangle {0 0 256 256}
set_source pattern
fill
pattern_destroy
pattern_create_radial {115.2 102.4 25.6 102.4 102.4 128.0}
pattern_add_color_stop_rgba {0 1 1 1 1}
pattern_add_color_stop_rgba {1 0 0 0 1}
set_source pattern
arc {128.0 128.0 76.8 0 360}
fill
pattern_destroy
"
return $actions
}
proc action:image {} {
lassign [gnocl::pixBuf fileInfo romedalen.png] w h t
set actions "
image_surface_create_from_png romedalen.png
translate {128 128}
rotate 45
scale {[expr 256.0/$w] [expr 256.0/$h]}
translate {[expr -0.5*$h] [expr -0.5*$h]}
set_source_surface {0 0}
paint
"
#puts $actions
return $actions
}
proc action:multi_segment_caps {} {
set actions "
set_source_rgba {0 0 0 1}
move_to {50 75}
line_to {200 75}
move_to {50 125}
line_to {200 125}
move_to {50 175}
line_to {200 175}
set_line_width 30
set_line_cap round
stroke
"
return $actions
}
proc action:line_cap {} {
set actions "
set_source_rgba {0 0 0 1}
set_line_width {30.0}
# {/* default */}
set_line_cap butt
move_to {64.0 50.0} line_to {64.0 200.0}
stroke
set_line_cap round
move_to {128.0 50.0} line_to {128.0 200.0}
stroke
set_line_cap square
move_to {192.0 50.0} line_to {192.0 200.0}
stroke
# {/*draw helping lines */}
set_source_rgb {1 0.2 0.2}
set_line_width {2.56}
move_to {64.0 50.0} line_to {64.0 200.0}
move_to {128.0 50.0} line_to {128.0 200.0}
move_to {192.0 50.0} line_to {192.0 200.0}
stroke
"
return $actions
}
proc action:line_join {} {
set actions "
set_source_rgba {0 0 0 1}
set_line_width {40.96}
move_to {76.8 84.48}
rel_line_to {51.2 -51.2}
rel_line_to {51.2 51.2}
set_line_join miter # default
stroke
move_to {76.8 161.28}
rel_line_to {51.2 -51.2}
rel_line_to {51.2 51.2}
set_line_join bevel
stroke
move_to {76.8 238.08}
rel_line_to {51.2 -51.2}
rel_line_to {51.2 51.2}
set_line_join round
stroke
"
return $actions
}
proc action:clear {} {
set actions "
set_source_rgba {0 0 0 0}
rectangle {0 0 256 256}
fill
"
return $actions
}
proc wdg:comboBox {} {
foreach item [lsort [info procs action*]] {
lappend itemList [list $item $item]
}
set container [gnocl::hBox]
set box [gnocl::vBox]
set txt [gnocl::text -baseFont {Mono 9} -widthRequest 256 ] ;# -baseColor black -textColor green
set pb [gnocl::pixBuf new -width 256 -height 256 -alpha 1]
set im [gnocl::image -image %?$pb ]
$pb actions [[lindex [lindex $itemList 0] 1]]
set cb [gnocl::comboBox -itemList $itemList -variable comboBoxVar -data_ $pb -data "$pb $im $txt" -onChanged {
[lindex %d 0] clear
[lindex %d 0] actions [%v]
[lindex %d 1] configure -image %?[lindex %d 0]
[lindex %d 2] set [%v]
} ]
$box add $cb
$box add $im
#$box add [gnocl::label -textVariable comboBoxVar] -expand 0.5
$cb changed
$container add $box
$container add $txt -fill 1 -expand 1
return "$container -fill 1 -expand 1"
}
gnocl::window -child [wdg:comboBox] -width 750