-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Emoji cause text to extend beyond edge of bounding box #7
Comments
@xtian Can you show me code of the example? |
@hidakatsuya, here's the code where this is happening in my project: @document.bounding_box [0.9.in, 1.1.in], { width: 6.in, height: 0.7.in } do
@document.font 'Sentinel Book', size: 9 do
@document.text text, align: :center, valign: :center, leading: 2, inline_format: true
end
end |
@xtian Thanks so much! |
This problem is very difficult. There is no easy implementation to fix, but you may be able to resolve to use a Japanese font like ipag.ttf. # Install ipag.ttf
@document.font_families.update('IPAGothic' => { normal: 'ipag.ttf' })
# :
@document.text 'foo bar <font name="IPAGothic">😀</font>', inline_format: true |
Yeah, I looked into fixing this myself and couldn't find a straightforward way to do it. Frustrating! Thanks for the Japanese font tip. I'm no longer working on the project where I was having this issue, but maybe it will help someone else. Feel free to close this issue if you'd like. |
Hey! We're also facing this issue and were wondering if you have maybe some details where and why exactly this is happening, @hidakatsuya. |
OK, please wait for a while as I will investigate this issue again. |
Did you ever have a chance to look into this again? |
@aried3r @brandoncc @hidakatsuya This most certainly happens due to the line wrapping algorithm not knowing the size of the emoji images together with the missing width information for the emoji characters. |
Thanks @gettalong! |
I managed to make it work by overriding some methods from Prawn 2.3.0. The idea was to replace emojis with classic chars so Prawn manages to wrap lines correctly. Here is my (naive) proposal: Requirements
Prawn emojiFix cursor position by adding Prawn::Emoji::Drawer.class_eval do
def draw_emoji(emoji_char, at:)
emoji_image = Emoji::Image.new(emoji_char)
emoji_image.render(document, at: at)
document.font_size + document.character_spacing
end
end PrawnCopy this code into # Replace emojis unicodes with char to fix the caclculation of the length
# you should use the same unicode emoji regex as the one set up in your prawn-emoji config
def replace_emojis_with_text(text)
text.gsub(Unicode::Emoji::REGEX_WELL_FORMED_INCLUDE_TEXT) { 'XX' }
end
# Parse text and index emojis position and length in a hash
# example : {10 => 1, 35 => 5}
# means we have an emoji of length 1 at the index 10
# and we have an emoji of length 5 at the index 35
def index_emojis(text)
emojis_indexes = {}
text.to_enum(:scan, Unicode::Emoji::REGEX_WELL_FORMED_INCLUDE_TEXT).map do |m|
m.length.times do |i|
emojis_indexes[$`.size + i] = i.zero? ? m.length : 0
end
end
emojis_indexes
end
Prawn::Text::Formatted::Arranger.class_eval do
# This method repositions the x cursor after writing text fragments
def fragment_measurements=(fragment)
apply_font_settings(fragment) do
fragment.width = @document.width_of(
replace_emojis_with_text(fragment.text),
kerning: @kerning
)
fragment.line_height = @document.font.height
fragment.descender = @document.font.descender
fragment.ascender = @document.font.ascender
end
end
end
Prawn::Text::Formatted::LineWrap.class_eval do
# render char if line has enough space left
def append_char(char)
# kerning doesn't make sense in the context of a single character
char_width = @document.width_of(replace_emojis_with_text(char))
if @accumulated_width + char_width <= @width
@accumulated_width += char_width
@fragment_output << char
true
else
false
end
end
# parse segment and tries to render char
def wrap_by_char(segment)
emojis_indexes = index_emojis(segment)
segment.each_char.with_index do |char, index|
if emojis_indexes[index].present?
# if length == 0, emoji has already been processed
next if emojis_indexes[index].zero?
char = segment[index, emojis_indexes[index]]
end
break unless append_char(char)
end
end
# Regex to test if text contains more than one word
def scan_pattern(encoding = ::Encoding::UTF_8)
ebc = break_chars(encoding)
eshy = soft_hyphen(encoding)
ehy = hyphen(encoding)
ews = whitespace(encoding)
patterns = [
"[^#{ebc}]+#{eshy}",
"[^#{ebc}]+#{ehy}+",
"[^#{ebc}]+",
"[#{ews}]+",
"#{ehy}+[^#{ebc}]*",
eshy.to_s
]
pattern = patterns
.map { |p| p.encode(encoding) }
.join('|')
Regexp.new("#{Unicode::Emoji::REGEX_WELL_FORMED_INCLUDE_TEXT}|#{pattern}")
end
# This method calculates the line remaining space to decide when to start a new line
def add_fragment_to_line(fragment)
if fragment == ''
true
elsif fragment == "\n"
@newline_encountered = true
false
else
tokenize(fragment).each do |segment|
segment_width = if segment == zero_width_space(segment.encoding)
0
else
@document.width_of(replace_emojis_with_text(segment), kerning: @kerning)
end
if @accumulated_width + segment_width <= @width
@accumulated_width += segment_width
shy = soft_hyphen(segment.encoding)
if segment[-1] == shy
sh_width = @document.width_of(shy, kerning: @kerning)
@accumulated_width -= sh_width
end
@fragment_output += segment
else
if @accumulated_width.zero? && @line_contains_more_than_one_word
@line_contains_more_than_one_word = false
end
end_of_the_line_reached(segment)
fragment_finished(fragment)
return false
end
end
fragment_finished(fragment)
true
end
end
end
Prawn::Text::Formatted::Box.class_eval do
# This methods parses the text and test that every char symbol is included in the current font
# If a symbol is not included it is going to parse the provided fallback fonts
# We needed to override this method so it does not break in half our Emojis
# We also forced the font 'ipag' on the Emojis so they can be properly rendered in a text box
def analyze_glyphs_for_fallback_font_support(hash)
font_glyph_pairs = []
original_font = @document.font.family
fragment_font = hash[:font] || original_font
fallback_fonts = @fallback_fonts.dup
# always default back to the current font if the glyph is missing from
# all fonts
fallback_fonts << fragment_font
@document.save_font do
emojis_indexes = index_emojis(hash[:text])
# parse each char to test if we need to use a fallback font
hash[:text].each_char.with_index do |char, index|
if emojis_indexes[index].present?
# if length == 0, emoji has already been processed
next if emojis_indexes[index].zero?
# Force japanese font ipag on emojis
font_glyph_pairs << [
'ipag', hash[:text][index, emojis_indexes[index]]
]
else
font_glyph_pairs << [
find_font_for_this_glyph(
char,
fragment_font,
fallback_fonts.dup
),
char
]
end
end
end
# Don't add a :font to fragments if it wasn't there originally
if hash[:font].nil?
font_glyph_pairs.each do |pair|
pair[0] = nil if pair[0] == original_font
end
end
form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
end
end Hoping this can help 🙏 |
Wow @ggmomo, that is awesome. I hope this moves the emoji conversation forward! |
Rendering an emoji in a line causes it to extend beyond the boundaries of its containing bounding box.
Example:
The text was updated successfully, but these errors were encountered: