Skip to content

Commit

Permalink
Take width of single line text field widget into account when auto-si…
Browse files Browse the repository at this point in the history
…zing

The current implementation only takes care of making the text not too
tall for the widget. However, if the field value is longer it is
truncated by HexaPDF whereas other PDF libraries/apps shrink the font
size.

This change adjusts HexaPDF's behaviour to be more in line with other
common PDF libraries/apps.
  • Loading branch information
gettalong committed Jul 29, 2023
1 parent e889afd commit fd53a7a
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 26 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
* Validation of standard encryption dictionary to auto-correct invalid /U and
/O fields in case they are padded with zeros
* [HexaPDF::Document#wrap] handling of sub-type mapping in case of missing type
* [HexaPDF::Type::AcroForm::AppearanceGenerator] to also take a text field
widget's width into account when auto-sizing


## 0.32.2 - 2023-05-06
Expand Down
42 changes: 21 additions & 21 deletions lib/hexapdf/type/acro_form/appearance_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def create_push_button_appearances
def create_text_appearances
default_resources = @document.acro_form.default_resources
font, font_size, font_color = retrieve_font_information(default_resources)
style = HexaPDF::Layout::Style.new(font: font, fill_color: font_color)
style = HexaPDF::Layout::Style.new(font: font, font_size: font_size, fill_color: font_color)
border_style = @widget.border_style
padding = [1, border_style.width].max

Expand All @@ -226,8 +226,6 @@ def create_text_appearances

canvas = form.canvas
apply_background_and_border(border_style, canvas)
style.font_size = calculate_font_size(font, font_size, height, border_style)
style.clear_cache

canvas.marked_content_sequence(:Tx) do
if @field.field_value || @field.concrete_field_type == :list_box
Expand Down Expand Up @@ -362,6 +360,7 @@ def draw_marker(canvas, width, height, border_width, marker_style)
def draw_single_line_text(canvas, width, height, style, padding)
value, text_color = apply_javascript_formatting(@field.field_value)
style.fill_color = text_color if text_color
calculate_and_apply_font_size(value, style, width, height, padding)
fragment = HexaPDF::Layout::TextFragment.create(value, style)

if @field.concrete_field_type == :comb_text_field
Expand Down Expand Up @@ -431,6 +430,11 @@ def draw_multiline_text(canvas, width, height, style, padding)

# Draws the visible option items of the list box in the widget's rectangle.
def draw_list_box(canvas, width, height, style, padding)
if style.font_size == 0
style.font_size = 12 # Seems to be Adobe's default
style.clear_cache
end

option_items = @field.option_items
top_index = @field.list_box_top_index
items = [Layout::TextFragment.create(option_items[top_index..-1].join("\n"), style)]
Expand Down Expand Up @@ -475,24 +479,20 @@ def retrieve_font_information(resources)
[font, font_size, font_color]
end

# Calculates the font size for text fields based on the font and font size of the default
# appearance string, the annotation rectangle's height and the border style.
def calculate_font_size(font, font_size, height, border_style)
if font_size == 0
case @field.concrete_field_type
when :multiline_text_field
0 # Handled by multiline drawing code
when :list_box
12 # Seems to be Adobe's default
else
unit_font_size = (font.wrapped_font.bounding_box[3] - font.wrapped_font.bounding_box[1]) *
font.scaling_factor / 1000.0
# The constant factor was found empirically by checking what Adobe Reader etc. do
(height - 2 * border_style.width) / unit_font_size * 0.83
end
else
font_size
end
# Calculates the font size for single line text fields using auto-sizing, based on the font
# and font size of the default appearance string, the annotation rectangle's height and
# width and the given padding. The font size is then applied to the provided style object.
def calculate_and_apply_font_size(value, style, width, height, padding)
return if style.font_size != 0

font = style.font
unit_font_size = (font.wrapped_font.bounding_box[3] - font.wrapped_font.bounding_box[1]) *
font.scaling_factor / 1000.0
# The constant factor was found empirically by checking what Adobe Reader etc. do
style.font_size = (height - 2 * padding) / unit_font_size * 0.85
fragment = HexaPDF::Layout::TextFragment.create(value, style)
style.font_size = [style.font_size, style.font_size * (width - 4 * padding) / fragment.width].min
style.clear_cache
end

# Handles Javascript formatting routines for single-line text fields.
Expand Down
18 changes: 13 additions & 5 deletions test/hexapdf/type/acro_form/test_appearance_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -467,12 +467,20 @@ def check_rotation(angle, width, height, matrix)
it "calculates the font size based on the rectangle height and border width" do
@generator.create_appearances
assert_operators(@widget[:AP][:N].stream,
[:set_font_and_size, [:F1, 12.923875]],
[:set_font_and_size, [:F1, 13.235294]],
range: 5)
@widget.border_style(width: 2, color: :transparent)
@generator.create_appearances
assert_operators(@widget[:AP][:N].stream,
[:set_font_and_size, [:F1, 11.487889]],
[:set_font_and_size, [:F1, 11.764706]],
range: 5)
end

it "shrinks the font size if necessary to fit the rectangle width" do
@field.field_value = "This is some arbitrary, long text"
@generator.create_appearances
assert_operators(@widget[:AP][:N].stream,
[:set_font_and_size, [:F1, 6.909955]],
range: 5)
end

Expand Down Expand Up @@ -573,7 +581,7 @@ def assert_format(arg_string, result, range)
assert_format("2, 3, #{style}, 0, \"E\", true",
[[:set_device_rgb_non_stroking_color, [style % 2, 0.0, 0.0]],
[:begin_text],
[:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
[:set_text_matrix, [1, 0, 0, 1, 2, 3.183272]],
[:show_text, [result]]], 6..9)
end
end
Expand All @@ -598,10 +606,10 @@ def assert_format(arg_string, result, range)
[:append_rectangle, [1, 1, 98, 9.25]],
[:clip_path_non_zero],
[:end_path],
[:set_font_and_size, [:F1, 6.641436]],
[:set_font_and_size, [:F1, 6.801471]],
[:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
[:begin_text],
[:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
[:set_text_matrix, [1, 0, 0, 1, 2, 3.183272]],
[:show_text, ["Text"]],
[:end_text],
[:restore_graphics_state],
Expand Down

0 comments on commit fd53a7a

Please sign in to comment.