Перевод размеров текста из пикселей в единица AutoCAD

Автор Тема: Перевод размеров текста из пикселей в единица AutoCAD  (Прочитано 12739 раз)

0 Пользователей и 2 Гостей просматривают эту тему.

Тема содержит сообщение с Решением. Нажмите здесь чтобы посмотреть его.

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Всем привет. Уже неделю мучаюсь с этим вопросом. Работу я делаю для Ревита, но в Автокаде тоже самое.
Итак, стоит задача - получить ячейку в файле экселя и отрисовать её в автокаде (т.е. и рамочку и текст).
Создаю файл экселя, заполняю первую ячейку:

Размеры ячейки в самом файле экселя: ширина - 19,43 ед. (141 пиксель); высота - 15,00 поинтов (20 пиксель). Единицы измерения ширины ячеек в экселе - это отдельная песня:
Цитировать
Ширина столбца на листе может иметь значение от 0 до 255. Оно соответствует числу знаков, которые могут отображаться в ячейке, отформатированной с использованием стандартного шрифта. Ширина столбца по умолчанию составляет 8,43 знака
Благо, Office.Interop.Excel выдает ширину еще и в поинтах, поэтому одной головной болью меньше.

Сделал небольшой тестовый проект (приложу к теме), который читает первую ячейку в эксель файле. В нем же я пытаюсь перевести единицы экселя в нужные мне. В частности, в миллиметры. Проект прикладываю к теме.
Для начала перевожу размеры ячейки:
Код - C# [Выбрать]
  1. Excel.Workbook wb = eXapp.Workbooks.Open(fileName);
  2. Excel.Worksheet ws = wb.Worksheets[1];
  3. Excel.Range cell = ws.Cells[1, 1];
  4. float dpiX = GetDpiX();
  5. float dpiY = GetDpiY();
  6. var str = cell.Text;
  7. Msg($"First cell text: {str}");
  8. var cellWidthInPoints = cell.Width;
  9. var cellHeightInPoints = cell.Height;
  10. Msg($"Cell width in points: {cellWidthInPoints}");
  11. Msg($"Cell height in points: {cellHeightInPoints}");
  12. var cellWidthInPixels = ConvertPointToPixels(cellWidthInPoints, dpiX);
  13. var cellHeightInPixels = ConvertPointToPixels(cellHeightInPoints, dpiY);
  14. Msg($"Cell width in pixels: {cellWidthInPixels}");
  15. Msg($"Cell height in pixels: {cellHeightInPixels}");
  16. Msg($"Cell height in mm: {ConvertPixelsToMm(cellHeightInPixels, dpiY)}");
  17. Msg($"Cell width in mm: {ConvertPixelsToMm(cellWidthInPixels, dpiX)}");

В итоге получаю размер ячейки в мм: 5,291667х37,30625. Отрисовываю её в автокаде.

Вот дальше начинается просто непонятная мне хрень - нужно получить размеры текста. И как бы не было это странно, но ширину всей строки получить получается, а высоту - НЕТ!
Для измерения ширины строки нужно использовать класс Font и класс Graphics из библиотеки System.Drawing. Тут тоже пришлось помучаться, но нашел наиболее точный вариант измерения:
Код - C# [Выбрать]
  1. var fW2 = _MeasureDisplayStringWidth(Graphics.FromHwnd(IntPtr.Zero), str, font);
  2. Msg($"text width 2: {fW2}");
  3. var textWidthInMm = ConvertPixelsToMm(fW2, dpiX);
  4. ....
  5. protected int _MeasureDisplayStringWidth(Graphics graphics, string text, Font font)
  6. {
  7.     if (text == "")
  8.         return 0;
  9.  
  10.     StringFormat format = new StringFormat(StringFormat.GenericDefault);
  11.     RectangleF rect = new RectangleF(0, 0, 1000, 1000);
  12.     CharacterRange[] ranges = { new CharacterRange(0, text.Length) };
  13.     Region[] regions = new Region[1];
  14.  
  15.     format.SetMeasurableCharacterRanges(ranges);
  16.     format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
  17.  
  18.     regions = graphics.MeasureCharacterRanges(text, font, rect, format);
  19.     rect = regions[0].GetBounds(graphics);
  20.    
  21.     return (int)(rect.Right);
  22. }

При измерении ширины строки получаю 34,925 мм

Возвращаюсь в автокад и создаю текст. Подгонял на глаз, чтобы примерно было похоже на эксель. Высоту текста подгонял только в десятках (в сотнях было бы точнее):



Т.е. высоту текста в мм я должен получить примерно 2,6 мм. Вот тут и проблема - сколько я не пробую, сколько ни ищу информацию - но у меня никак этого не получается. Все возможные попытки выдают мне высоту текста много больше!

Может я просто уже устал и не вижу чего-то очевидного ( Надеюсь на свежий взгляд других пытливых умов.

З.Ы. Я многие методы не привел в тексте. Все есть в приложенном проекте

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
А что у тебя возвращает rect.Height?
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
А что у тебя возвращает rect.Height?
18
Тут еще немного непонятно с единицами. rect дает размеры в тех единицах, которые установлены в Font. Вроде. Тут нужно еще поэкспериментировать

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
А почему у тебя return (int)(rect.Right); ??? Не понятно зачем ты приводишь к целым и еще меньше понятно почему .Right а не .Width
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
А почему у тебя return (int)(rect.Right); ??? Не понятно зачем ты приводишь к целым и еще меньше понятно почему .Right а не .Width
Да это не суть))
Переделал метод:
Код - C# [Выбрать]
  1. protected RectangleF MeasureDisplayString(Graphics graphics, string text, Font font)
  2. {
  3.     if (text == "")
  4.         return RectangleF.Empty;
  5.  
  6.     StringFormat format = new StringFormat(StringFormat.GenericDefault);
  7.     RectangleF rect = new RectangleF(0, 0, 1000, 1000);
  8.     CharacterRange[] ranges = { new CharacterRange(0, text.Length) };
  9.     Region[] regions = new Region[1];
  10.  
  11.     format.SetMeasurableCharacterRanges(ranges);
  12.     format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
  13.  
  14.     regions = graphics.MeasureCharacterRanges(text, font, rect, format);
  15.     rect = regions[0].GetBounds(graphics);
  16.    
  17.     return rect;
  18. }
Если я задаю шрифт так:
Код - C# [Выбрать]
  1. Font font = new Font(GetFontFamily(fontName),(float)xLfontSize
  2.     ,GetFontStyle((bool)cell.Font.Bold,(bool)cell.Font.Italic,(bool)cell.Font.Strikethrough, false)
  3.     ,GraphicsUnit.Point);
То получаю такие значения:
Цитировать
rectangle width: 130
rectangle height: 18
rectangle right: 132
rectangle bottom: 18
Если задаю шрифт так:
Код - C# [Выбрать]
  1. Font font = new Font(GetFontFamily(fontName),(float)xLfontSize
  2.     ,GetFontStyle((bool)cell.Font.Bold,(bool)cell.Font.Italic,(bool)cell.Font.Strikethrough, false)
  3.     ,GraphicsUnit.Pixel);
То получаю такие:
Цитировать
rectangle width: 98
rectangle height: 14
rectangle right: 100
rectangle bottom: 14
В первом случае это значит поинты, во втором - пиксели. Но если переводить поинты в мм, то я получаю 45.861111111 мм - это слишком много
Во втором случае вроде как пиксели. Но если переводить пиксели в мм, то получаю 25.929166667, что наоборот - слишком мало

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Задал шрифт так:
Код - C# [Выбрать]
  1. Font font = new Font(GetFontFamily(fontName)
  2.     //,(float)xLfontSize
  3.     ,14.666667f
  4.     ,GetFontStyle((bool)cell.Font.Bold,(bool)cell.Font.Italic,(bool)cell.Font.Strikethrough, false)
  5.     ,GraphicsUnit.Pixel);
где 11 поинтов = 14,66667 пикселей. В итоге получил такие значения:
Цитировать
rectangle width: 130
rectangle height: 18
rectangle right: 132
rectangle bottom: 18
130 пикселей = 34.395833333 мм. Как раз то, что нужно.
Но вот 18 пикселей = 4.7625 мм. И это совсем не рядом =((

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Шрифт и в Excel и в AutoCAD один и тот же? И степень сжатия одинакова?
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Шрифт и в Excel и в AutoCAD один и тот же? И степень сжатия одинакова?
В эксели нет степени сжатия у шрифта. Как ни странно
Во всем остальном - да, конечно все одинаково. Я даже делал скриншот с экселя и подкладывал его в автокаде - прям тютелька в тютельку. Плюс-минус 0,1 мм. Экспериментально уточнил высоту шрифта, которую должен получить - 2,57 мм
Но у меня никак из 18 пикселей не получается 2,57 мм. Что-то тут еще должно участвовать

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Такое впечатление, что еще участвуют отступы по 1 мм сверху и снизу текста.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Такое впечатление, что еще участвуют отступы по 1 мм сверху и снизу текста.
У меня сейчас другая идея возникла. Есть ощущение, что автокад/ревит измеряет шрифт "восходящим" размером только. Без учета хвостика вниз и выступов вверх. Например для буквы Й
Исходя вот из этой статьи сейчас еще попробую поэкспериментировать

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Код - C# [Выбрать]
  1. var ascent = font.FontFamily.GetCellAscent(font.Style);
  2. var ascentPixels = font.Size * ascent / font.FontFamily.GetEmHeight(font.Style);
  3. Msg($"Ascent: {ascent}");
  4. Msg($"Ascent pixels: {ascentPixels}");
  5. var ascentMm = ConvertPixelsToMm(ascentPixels, dpiY);
  6. Msg($"ascent mm: {ascentMm}");

Цитировать
Ascent: 1950
Ascent pixels: 13,96484
ascent mm: 3,69486490885417

Опять не оно(

Отмечено как Решение Александр Пекшев aka Modis 28-12-2017, 16:34:10

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Я хочу выразить "ОГРОМНУЮ благодарность" сотрудникам автодеска, которые решили измерять высоту текста в попугаях!
Андрей Бушман сказал, что он когда-то уже пробовал выяснить как они измеряют высоту и как её переводить, но так ничего и не получилось.
Я нашел одну статью про метрику текста в WPF и вот возможностей этой метрики вроде чуть больше.
Вот картинка из статьи, показывающая положение свойств шрифта:

Поигравшись с автокадом пришел к выводу, что значение высоты, которую они используют равна расстоянию от y_caps до y_baseline на картинке.
Сделал такой метод:
Код - C# [Выбрать]
  1. private double CalculateFontHeightInPixels(System.Drawing.Font font, string str)
  2. {
  3.     //http://csharphelper.com/blog/2015/05/get-font-metrics-in-a-wpf-program-using-c/
  4.  
  5.     var emSize = font.FontFamily.GetEmHeight(font.Style);
  6.     System.Windows.Media.FontFamily fontFamily = new System.Windows.Media.FontFamily(font.Name);
  7.     System.Windows.FontStyle fontStyle = FontStyles.Normal;
  8.     if(font.Italic) fontStyle = FontStyles.Italic;
  9.     System.Windows.FontWeight fontWeight = FontWeights.Normal;
  10.     if (font.Bold) fontWeight = FontWeights.Bold;
  11.     System.Windows.FontStretch fontStretch = FontStretches.Normal;
  12.  
  13.     Typeface typeface = new Typeface(fontFamily, fontStyle,fontWeight, fontStretch);
  14.    
  15.     FormattedText formattedText = new FormattedText(
  16.         str,
  17.         CultureInfo.GetCultureInfo("en-us"),
  18.         FlowDirection.LeftToRight,
  19.         typeface,
  20.         emSize,
  21.         System.Windows.Media.Brushes.Black);
  22.     var baseline = formattedText.Baseline;
  23.     var caps = baseline + typeface.CapsHeight * emSize;
  24.     var y = caps - baseline;
  25.     var heightInPixels = font.Size * y / emSize;
  26.  
  27.     return heightInPixels;
  28. }
И получил значение высоты шрифта (в указанных границах) = 2,45187451060822 мм. Это оооочень близко к тому, что я намерил "глазами" (2,67 мм). Проверял еще с Times New Roman при высоте в 14 поинтов - дало такой-же результат с погрешность в ~0,1-0,12 мм

Может мне просто повезло, что я получил примерно нужные значения из неясных вычислений (к тому-же перемешав методы из System.Drawing с методами из System.Windows.Media), но пока-что это самые близкие к правде результаты

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Я хочу выразить "ОГРОМНУЮ благодарность" сотрудникам автодеска, которые решили измерять высоту текста в попугаях!
Это настолько древняя технология, что сейчас даже концов её не найти. Более того, она намного древнее чем TrueType-шрифты (не говоря уже про WPF), так как использовалась еще во времена DOS.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Я хочу выразить "ОГРОМНУЮ благодарность" сотрудникам автодеска, которые решили измерять высоту текста в попугаях!
Это настолько древняя технология, что сейчас даже концов её не найти. Более того, она намного древнее чем TrueType-шрифты (не говоря уже про WPF), так как использовалась еще во времена DOS.
Жаль, что при этом нет разъяснений как там и что устроено. Помогло бы

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение