Как рассчитать ширину строки в Ruby?

String.length только скажет мне, сколько символов в строке. (Фактически, до Ruby 1.9 он сообщал мне только количество байтов, что было еще менее полезно.)

Я действительно хотел бы узнать, сколько строк в ширину. Например:

'foo'.width
# => 3

'moo'.width
# => 3.5          # m's, w's, etc. are wide

'foi'.width
# => 2.5          # i's, j's, etc. are narrow

'foo bar'.width
# => 6.25         # spaces are very narrow

Еще лучше было бы, если бы я мог получить первый n en строки:

'foo'[0, 2.en]
# => "fo"

'filial'[0, 3.en]
# => "fili"

'foo bar baz'[0, 4.5en]
# => "foo b"

И все же было бы лучше, если бы я мог все продумать. Кто-то думает, что интервал должен быть 0,25 пенни, кто-то думает, что он должен быть 0,33 и т. д.

Это должно сильно зависеть от шрифта, используемого для рендеринга строки, не так ли?

JesperE 18.12.2008 22:21

И вы хотите это независимо от шрифта? Я не думаю, что это возможно

Keltia 18.12.2008 22:21

Я согласен с другими комментаторами, если вы не знаете шрифт, это невозможно.

Robert K 18.12.2008 22:32

Вариант 1 предполагает, что если вы используете шрифт фиксированной ширины, вы должны использовать .legnth вместо .width. Вариант 2 - иметь объект Font. AFAIK, в Ruby нет класса Font.

James A. Rosen 18.12.2008 23:59
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
7
4
4 490
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вы можете попытаться создать стандартизированную «таблицу пропорций ширины» для вычисления приближения, в основном вам нужно сохранить ширину каждого символа, а затем пересечь строку, складывая ширину.

Я нашел эту таблицу здесь:

Left, Width, Advance values for ArialBD16 'c' through 'm'
Letter  Left    Width   Advance
c        1       7       9
d        1       8       10
e        1       8       9
f        0       6       5
g        0       9       10
h        1       8       10
i        1       2       4
j       -1       4       4
k        1       8       9
l        1       2       4
m        1       12      14

Если вы хотите стать серьезным, я бы начал с просмотра webkit, gecko и OO.org, но я думаю, что алгоритмы кернинга и расчета размера нетривиальны.

Ответ принят как подходящий

Вы должны использовать гем RMagick для рендеринга объекта "Draw" с использованием нужного шрифта (вы можете загружать файлы .ttf и т. д.)

Код будет выглядеть примерно так:

   the_text = "TheTextYouWantTheWidthOf"
   label = Draw.new
   label.font = "Vera" #you can also specify a file name... check the rmagick docs to be sure
   label.text_antialias(true)
   label.font_style=Magick::NormalStyle
   label.font_weight=Magick::BoldWeight
   label.gravity=Magick::CenterGravity
   label.text(0,0,the_text)
   metrics = label.get_type_metrics(the_text)
   width = metrics.width
   height = metrics.height

Вы можете увидеть это в действии в моем конструкторе кнопок здесь: http://risingcode.com/button/everybodywangchungtonite

просто не забудьте установить размер шрифта с помощью «label.pointsize = THE_SIZE», иначе RMagick по умолчанию будет использовать размер шрифта 12.

catwood 14.01.2010 08:12

ссылка производителя кнопки кажется мертва

Huliax 22.01.2016 18:26

Если у вас установлен ImageMagick, вы можете получить доступ к этой информации из командной строки.

$ convert xc: -font ./.fonts/HelveticaRoundedLTStd-Bd.otf  -pointsize 24 -debug annotate -annotate 0 'MyTestString' null: 2>&1
2010-11-02T19:17:48+00:00 0:00.010 0.010u 6.6.5 Annotate convert[22496]: annotate.c/RenderFreetype/1155/Annotate
  Font ./.fonts/HelveticaRoundedLTStd-Bd.otf; font-encoding none; text-encoding none; pointsize 24
2010-11-02T19:17:48+00:00 0:00.010 0.010u 6.6.5 Annotate convert[22496]: annotate.c/GetTypeMetrics/736/Annotate
  Metrics: text: MyTestString; width: 157; height: 29; ascent: 18; descent: -7; max advance: 24; bounds: 0,-5  20,17; origin: 158,0; pixels per em: 24,24; underline position: -1.5625; underline thickness: 0.78125
2010-11-02T19:17:48+00:00 0:00.010 0.010u 6.6.5 Annotate convert[22496]: annotate.c/RenderFreetype/1155/Annotate
  Font ./.fonts/HelveticaRoundedLTStd-Bd.otf; font-encoding none; text-encoding none; pointsize 24

Чтобы сделать это из Ruby, используйте обратные кавычки:

result = `convert xc: -font #{path_to_font} -pointsize #{size} -debug annotate -annotate 0 '#{string}' null: 2>&1`
if result =~ /width: (\d+);/
  $1
end

Поскольку драгоценный камень rmagick в настоящее время не обслуживается, это привлекательный вариант.

ipd 09.03.2018 00:23

Это хорошая проблема!

Я пытаюсь решить эту проблему, используя pango / cairo в ruby ​​для вывода SVG. Я, вероятно, собираюсь использовать pango для вычисления ширины, а затем использовать простой элемент svg.

Я использую следующий код:

require "cairo"
require "pango"

paper = Cairo::Paper::A4_LANDSCAPE
TEXT = "Don't you love me anymore?"
def pac(surface)
        cr = Cairo::Context.new(surface)
        cr.select_font_face("Calibri",
                              Cairo::FONT_SLANT_NORMAL,
                              Cairo::FONT_WEIGHT_NORMAL)
    cr.set_font_size(12)
    extents = cr.text_extents(TEXT)
    puts extents
end

Cairo::ImageSurface.new(*paper.size("pt")) do |surface|
  cr = pac(surface)
end

что вы в конечном итоге использовали для этого? Пытаюсь сделать что-то подобное, но без RMagick (и легковесного).

ives 03.05.2019 10:56

Используйте гем ttfunk для чтения показателей из файла шрифта. Затем вы можете получить ширину строки текста в em. Вот мой запрос на добавление этого примера в драгоценный камень.

require 'rubygems'
require 'ttfunk'
require 'valuable'
# Everything you never wanted to know about glyphs:
# http://chanae.walon.org/pub/ttf/ttf_glyphs.htm

# this code is a substantial reworking of:
# https://github.com/prawnpdf/ttfunk/blob/master/examples/metrics.rb

class Font
  attr_reader :file

  def initialize(path_to_file)
    @file = TTFunk::File.open(path_to_file)
  end

  def width_of( string )
    string.split('').map{|char| character_width( char )}.inject{|sum, x| sum + x}
  end

  def character_width( character )
    width_in_units = ( horizontal_metrics.for( glyph_id( character )).advance_width )
    width_in_units.to_f / units_per_em
  end

  def units_per_em
    @u_per_em ||= file.header.units_per_em
  end

  def horizontal_metrics
    @hm = file.horizontal_metrics
  end

  def glyph_id(character)
    character_code = character.unpack("U*").first
    file.cmap.unicode.first[character_code]
  end
end

Вот он в действии:

>> din = Font.new("#{File.dirname(__FILE__)}/../../fonts/DIN/DINPro-Light.ttf")
>> din.width_of("Hypertension")
=> 5.832
# which is correct! Hypertension in that font takes up about 5.832 em! It's over by maybe ... 0.015.

Как только мне пришлось отобразить массив строк (содержащий ближайшие мировые дни, текущие именные дни и т. д.) В двух строках, поместив разрыв строки после соответствующей строки, мне пришлось определить совокупную ширину строк, напечатанных в Arial. Я открыл свой текстовый редактор, набрал алфавит и разделил символы на два класса в зависимости от их ширины в данном шрифте:

w = "023456789AÁBCDEFGHJKLMNOÓÖŐPQRSTUÚÜŰWZYaábcdeghksoóöőpqwuúüűzymn".chars.yield_self{|z| z.zip(Array.new(z.size){1.5})}.to_h.merge("1rfiíjltIÍ ".chars.yield_self{|z| z.zip(Array.new(z.size){1})}.to_h)
w.default=1
nntd=["01-21:A vallások világnapja", "01-19:Kanut", "Kenéz", "Margaréta", "Márió", "Máriusz", "Megyer", "Sára", "Szultána", "Vázsony"]
nntd.sort_by!{|z| z.chars.map{|q| w[q]}.sum}.reverse

Затем я смог определить положение переноса строки:

ind=nntd.collect.with_index.find_index{|z,i| nntd[0..i].join.chars.map{|q| w[q]}.sum >=nntd.join.chars.map{|q| w[q]}.sum/2}

t=[nntd[0..ind],nntd[ind+1..-1]].map{|z| z.join(",")}.join("\n")

В конце концов, у меня получился красивый сбалансированный выход, разделенный на две строки:

01–21: Валласок вилагнапья, 01–19: Маргарета, Султана Вазони, Мариуш, Мегьер, Кенес, Канут, Марио, Сара

Таким образом, я могу мгновенно проверить наступающие мировые дни и текущие именные дни.

Другие вопросы по теме