Сообщество программистов Autodesk в СНГ

ADN Club => AutoCAD .NET API => Тема начата: Александр Пекшев aka Modis от 28-12-2017, 11:30:41

Название: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 11:30:41
Всем привет. Уже неделю мучаюсь с этим вопросом. Работу я делаю для Ревита, но в Автокаде тоже самое.
Итак, стоит задача - получить ячейку в файле экселя и отрисовать её в автокаде (т.е. и рамочку и текст).
Создаю файл экселя, заполняю первую ячейку:
(https://s13.postimg.org/e46lr3k3n/Screenshot_7.png) (https://postimg.org/image/e46lr3k3n/)
Размеры ячейки в самом файле экселя: ширина - 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 мм

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

(https://s13.postimg.org/wxseo5v77/Screenshot_8.png) (https://postimg.org/image/wxseo5v77/)

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

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

З.Ы. Я многие методы не привел в тексте. Все есть в приложенном проекте
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Ривилис от 28-12-2017, 11:48:46
А что у тебя возвращает rect.Height?
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 11:55:03
А что у тебя возвращает rect.Height?
18
Тут еще немного непонятно с единицами. rect дает размеры в тех единицах, которые установлены в Font. Вроде. Тут нужно еще поэкспериментировать
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Ривилис от 28-12-2017, 12:05:10
А почему у тебя return (int)(rect.Right); ??? Не понятно зачем ты приводишь к целым и еще меньше понятно почему .Right а не .Width
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 12:15:25
А почему у тебя 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
В первом случае это значит поинты, во втором - пиксели. Но если переводить поинты в мм (http://www.unitconversion.org/typography/millimeters-to-points-computer-conversion.html), то я получаю 45.861111111 мм - это слишком много
Во втором случае вроде как пиксели. Но если переводить пиксели в мм (http://www.unitconversion.org/typography/pixels-x-to-millimeters-conversion.html), то получаю 25.929166667, что наоборот - слишком мало
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 12:21:45
Задал шрифт так:
Код - 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 мм. И это совсем не рядом =((
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Ривилис от 28-12-2017, 12:28:50
Шрифт и в Excel и в AutoCAD один и тот же? И степень сжатия одинакова?
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 12:33:09
Шрифт и в Excel и в AutoCAD один и тот же? И степень сжатия одинакова?
В эксели нет степени сжатия у шрифта. Как ни странно
Во всем остальном - да, конечно все одинаково. Я даже делал скриншот с экселя и подкладывал его в автокаде - прям тютелька в тютельку. Плюс-минус 0,1 мм. Экспериментально уточнил высоту шрифта, которую должен получить - 2,57 мм
Но у меня никак из 18 пикселей не получается 2,57 мм. Что-то тут еще должно участвовать
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Ривилис от 28-12-2017, 12:36:56
Такое впечатление, что еще участвуют отступы по 1 мм сверху и снизу текста.
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 12:58:12
Такое впечатление, что еще участвуют отступы по 1 мм сверху и снизу текста.
У меня сейчас другая идея возникла. Есть ощущение, что автокад/ревит измеряет шрифт "восходящим" размером только. Без учета хвостика вниз и выступов вверх. Например для буквы Й
Исходя вот из этой статьи (https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-obtain-font-metrics) сейчас еще попробую поэкспериментировать
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 13:03:47
Код - 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

Опять не оно(
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 15:23:40
Я хочу выразить "ОГРОМНУЮ благодарность" сотрудникам автодеска, которые решили измерять высоту текста в попугаях!
Андрей Бушман сказал, что он когда-то уже пробовал выяснить как они измеряют высоту и как её переводить, но так ничего и не получилось.
Я нашел одну статью про метрику текста в WPF (http://csharphelper.com/blog/2015/05/get-font-metrics-in-a-wpf-program-using-c/) и вот возможностей этой метрики вроде чуть больше.
Вот картинка из статьи, показывающая положение свойств шрифта:
(https://s13.postimg.org/evd41zvqb/howto_wpf_measure_formattedtext.png) (https://postimg.org/image/evd41zvqb/)
Поигравшись с автокадом пришел к выводу, что значение высоты, которую они используют равна расстоянию от 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), но пока-что это самые близкие к правде результаты
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Ривилис от 28-12-2017, 15:44:28
Я хочу выразить "ОГРОМНУЮ благодарность" сотрудникам автодеска, которые решили измерять высоту текста в попугаях!
Это настолько древняя технология, что сейчас даже концов её не найти. Более того, она намного древнее чем TrueType-шрифты (не говоря уже про WPF), так как использовалась еще во времена DOS.
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 15:45:36
Я хочу выразить "ОГРОМНУЮ благодарность" сотрудникам автодеска, которые решили измерять высоту текста в попугаях!
Это настолько древняя технология, что сейчас даже концов её не найти. Более того, она намного древнее чем TrueType-шрифты (не говоря уже про WPF), так как использовалась еще во времена DOS.
Жаль, что при этом нет разъяснений как там и что устроено. Помогло бы
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Ривилис от 28-12-2017, 15:56:49
Может быть полезным: http://docs.autodesk.com/ACDMAC/2011/ENU/ObjectARX%20Developer%27s%20Guide/filesXDG/WS1a9193826455f5ff128052610e798412bc-48f6.htm
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 21:46:46
Ну в общем проблема перевода ясна. Когда создавали автокад, windows еще не было и метрика шрифтов в автокаде была сделана самым логичным и правильным способом - по нормам черчения. Не видел зарубежных норм, но наши советские вполне подходят:
(https://s17.postimg.org/rvgqk4ypn/Screenshot_2.png) (https://postimg.org/image/rvgqk4ypn/)
Значение h на картинке как раз соответствует высоте в автокаде. В Ревите использовали такую же метрику.
А потом появилась Windows со своей собственной метрикой шрифтов. Не уверен, что высота задавалась сразу в поинтах, так как читал где-то, что эти самые поинты придумали в компании Adobe. И если брать уже метрику винды (да и типографии, кстати), то там уже полно всяких зависимостей - вплоть до разрешения экрана и даже масштаба отображения.
В Net есть методы измерения метрики, которые использовались до появления WPF (ссылку я давал в одном из ответов), но, к сожалению, в этой метрике отсутствует нужное значение - та самая h с картинки.
При появлении WPF ввели еще одну метрику. Ссылку с примером я также давал. Вот в этой метрике уже есть значения из которых можно высчитать h. Правда остается некоторая погрешность. Небольшая. Думаю, что точно высчитать нужную высоту для перевода либо не возможно, либо очень сложно. Очень много различных факторов на это влияют, вплоть до способа отрисовки программой шрифтов.
Может кому будет интересно и в будущем пригодится
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Ривилис от 28-12-2017, 21:53:57
Ну в общем проблема перевода ясна. Когда создавали автокад, windows еще не было и метрика шрифтов в автокаде была сделана самым логичным и правильным способом - по нормам черчения.
Та ты шо???!!! Даже не критикуешь Autodesk???
А как же это:
Я хочу выразить "ОГРОМНУЮ благодарность" сотрудникам автодеска, которые решили измерять высоту текста в попугаях!

???
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: Александр Пекшев aka Modis от 28-12-2017, 21:56:53
А как же это
Я был перевозбужден в тот момент, так как мучался с этим почти неделю и не принял во внимание тот факт, что автокад древнее винды =) Потом уже успокоился, все переварил и не вижу повода на автодеск обижаться ))
Название: Re: Перевод размеров текста из пикселей в единица AutoCAD
Отправлено: trir от 29-12-2017, 08:03:00
Цитировать
Не уверен, что высота задавалась сразу в поинтах, так как читал где-то, что эти самые поинты придумали в компании Adobe.
как бы Типографскому пункту (https://ru.wikipedia.org/wiki/%D0%A2%D0%B8%D0%BF%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D1%81%D0%BA%D0%B8%D0%B9_%D0%BF%D1%83%D0%BD%D0%BA%D1%82) уже почти пол тыши лет, но правда за это время их наплодилось несколько десятков и Adobe пришлось установить свой PostScript point