Skip to content

Commit da40263

Browse files
committed
Add support for custom anchors for PNGs and SVGs
1 parent 1132233 commit da40263

File tree

9 files changed

+95
-14
lines changed

9 files changed

+95
-14
lines changed

docs/args/anchor.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.. :orphan:
2+
3+
anchor
4+
default: ``:top_left``
5+
6+
the anchor of the image. Available values are ``:top_left``, ``:top_right``, ``:bottom_left``, ``:bottom_right``, and ``:middle`` or ``:center``. The first 4 options anchor the image relative to the corresponding corner, and ``:middle`` and ``:center`` set the anchor to the middle of the image

docs/dsl/png.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ file
1212

1313
file(s) to read in. As in :doc:`/arrays`, if this a single file, then it's use for every card in range. If the parameter is an Array of files, then each file is looked up for each card. If any of them are nil or '', nothing is done for that card.
1414

15+
.. include:: /args/anchor.rst
16+
1517
.. include:: /args/xy.rst
1618

1719
width

docs/dsl/svg.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ file
1919

2020
By default, if ``file`` is not found, a warning is logged. This behavior can be configured in :doc:`/config`
2121

22+
.. include:: /args/anchor.rst
23+
2224
.. include:: /args/xy.rst
2325

2426
data

lib/squib/args/scale_box.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class ScaleBox
1313
def self.parameters
1414
{
1515
x: 0, y: 0,
16-
width: :native, height: :native
16+
width: :native, height: :native,
17+
anchor: :top_left
1718
}
1819
end
1920

@@ -52,6 +53,11 @@ def validate_height(arg, i)
5253
raise 'height must be a number, :scale, :native, or :deck'
5354
end
5455

56+
def validate_anchor(arg, i)
57+
raise 'anchor must be one of :top_left, :top_right, :bottom_left, :bottom_right, or :center/:middle' unless [:top_left, :top_right, :bottom_left, :bottom_right, :center, :middle].include? arg
58+
arg
59+
end
60+
5561
end
5662

5763
end

lib/squib/dsl/png.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def initialize(deck, dsl_method)
2525
def self.accepted_params
2626
%i(
2727
file
28-
x y width height
28+
x y width height anchor
2929
alpha blend mask angle
3030
crop_x crop_y crop_width crop_height
3131
crop_corner_radius crop_corner_x_radius crop_corner_y_radius

lib/squib/dsl/svg.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def initialize(deck, dsl_method)
2626
def self.accepted_params
2727
%i(
2828
file
29-
x y width height
29+
x y width height anchor
3030
blend mask
3131
crop_x crop_y crop_width crop_height
3232
crop_corner_radius crop_corner_x_radius crop_corner_y_radius

lib/squib/graphics/image.rb

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,42 @@ def png(file, box, paint, trans)
3333
Squib.logger.debug {"RENDERING PNG: \n file: #{file}\n box: #{box}\n paint: #{paint}\n trans: #{trans}"}
3434
return if file.nil? or file.eql? ''
3535
png = Squib.cache_load_image(file)
36+
box.width = png.width.to_f if box.width == :native
37+
box.height = png.height.to_f if box.height == :native
38+
box.width = png.width.to_f * box.height.to_f / png.height.to_f if box.width == :scale
39+
box.height = png.height.to_f * box.width.to_f / png.width.to_f if box.height == :scale
40+
41+
scale_width = box.width.to_f / png.width.to_f
42+
scale_height = box.height.to_f / png.height.to_f
43+
warn_png_scale(file, scale_width, scale_height)
44+
45+
rotate_offset_x = 0
46+
rotate_offset_y = 0
47+
if [:top_right, :bottom_right].include? box.anchor
48+
box.x = box.x - box.width
49+
rotate_offset_x = box.width
50+
end
51+
if [:bottom_left, :bottom_right].include? box.anchor
52+
box.y = box.y - box.height
53+
rotate_offset_y = box.height
54+
end
55+
if [:center, :middle].include? box.anchor
56+
rotate_offset_x = (box.width / 2.0)
57+
box.x = box.x - rotate_offset_x
58+
rotate_offset_y = (box.height / 2.0)
59+
box.y = box.y - rotate_offset_y
60+
end
61+
3662
use_cairo do |cc|
3763
cc.translate(box.x, box.y)
38-
box.width = png.width.to_f if box.width == :native
39-
box.height = png.height.to_f if box.height == :native
40-
box.width = png.width.to_f * box.height.to_f / png.height.to_f if box.width == :scale
41-
box.height = png.height.to_f * box.width.to_f / png.width.to_f if box.height == :scale
42-
43-
scale_width = box.width.to_f / png.width.to_f
44-
scale_height = box.height.to_f / png.height.to_f
45-
warn_png_scale(file, scale_width, scale_height)
4664
cc.scale(scale_width, scale_height)
4765

48-
cc.rotate(trans.angle)
66+
if box.anchor == :top_left
67+
# Avoid doing useless translate([0, 0]) in Cairo and break regression tests
68+
cc.rotate(trans.angle)
69+
else
70+
cc.rotate_about(rotate_offset_x, rotate_offset_y, trans.angle)
71+
end
4972
cc.flip(trans.flip_vertical, trans.flip_horizontal, box.width / 2, box.height / 2)
5073
cc.translate(-box.x, -box.y)
5174

@@ -94,10 +117,31 @@ def svg(file, svg_args, box, paint, trans)
94117
box.height = svg.height.to_f * box.width.to_f / svg.width.to_f if box.height == :scale
95118
scale_width = box.width.to_f / svg.width.to_f
96119
scale_height = box.height.to_f / svg.height.to_f
120+
rotate_offset_x = 0
121+
rotate_offset_y = 0
122+
if [:top_right, :bottom_right].include? box.anchor
123+
box.x = box.x - box.width
124+
rotate_offset_x = box.width
125+
end
126+
if [:bottom_left, :bottom_right].include? box.anchor
127+
box.y = box.y - box.height
128+
rotate_offset_y = box.height
129+
end
130+
if [:center, :middle].include? box.anchor
131+
rotate_offset_x = (box.width / 2.0)
132+
box.x = box.x - rotate_offset_x
133+
rotate_offset_y = (box.height / 2.0)
134+
box.y = box.y - rotate_offset_y
135+
end
97136
use_cairo do |cc|
98137
cc.translate(box.x, box.y)
99138
cc.flip(trans.flip_vertical, trans.flip_horizontal, box.width / 2, box.height / 2)
100-
cc.rotate(trans.angle)
139+
if box.anchor == :top_left
140+
# Avoid doing useless translate([0, 0]) in Cairo and break regression tests
141+
cc.rotate(trans.angle)
142+
else
143+
cc.rotate_about(rotate_offset_x, rotate_offset_y, trans.angle)
144+
end
101145
cc.scale(scale_width, scale_height)
102146

103147
trans.crop_width = box.width if trans.crop_width == :native

samples/images/_images.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require 'squib'
22
require 'squib/sample_helpers'
33

4-
Squib::Deck.new(width: 1000, height: 3000) do
4+
Squib::Deck.new(width: 1000, height: 3200) do
55
draw_graph_paper width, height
66

77
sample "This a PNG.\nNo scaling is done by default." do |x, y|
@@ -99,6 +99,16 @@
9999
png file: 'with-alpha.png', mask: mask, x: x + 150, y: y, width: 150, height: :scale
100100
end
101101

102+
sample 'PNGs and SVGs are top-left-anchored by default but this can be changed.' do |x,y|
103+
rect x: x, y: y, width: 100, height: 100, # draw the crop line
104+
dash: '3 3', stroke_color: 'red', stroke_width: 3
105+
png x: x + 50, y: y + 50, width: 100, height: 100, file: 'angler-fish.png',
106+
anchor: :center, angle: - Math::PI / 4 - 0.2
107+
rect x: x + 150, y: y, width: 100, height: 100, # draw the crop line
108+
dash: '3 3', stroke_color: 'red', stroke_width: 3
109+
svg x: x + 250, y: y + 100, width: 100, height: 100, file: 'robot-golem.svg',
110+
anchor: :bottom_right, angle: Math::PI / 5
111+
end
102112

103113
save_png prefix: '_images_'
104114
end

spec/args/scale_box_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@
6464
expect { Squib::Args.extract_scale_box args, deck }.to raise_error('height must be a number, :scale, :native, or :deck')
6565
end
6666

67+
it 'allows setting anchors' do
68+
args = { anchor: :middle }
69+
box = Squib::Args.extract_scale_box args, deck
70+
expect(box).to have_attributes(anchor: [:middle])
71+
end
72+
73+
it 'disallows setting incorrect anchors' do
74+
args = { anchor: :midle }
75+
expect { Squib::Args.extract_scale_box args, deck }.to raise_error('anchor must be one of :top_left, :top_right, :bottom_left, :bottom_right, or :center/:middle')
76+
end
77+
6778
end
6879

6980

0 commit comments

Comments
 (0)