понедельник, 30 сентября 2013 г.

Об исходном коде

Роман Янковский (вот тут - http://blogs.embarcadero.com/vsevolodleonov/2013/09/30/roman_yankovsky_mvp/) сказал следующее:

"Так же проблема многих - это неумение относиться к коду без эмоций. Нужно всегда быть готовым спокойно выбросить свой код, если это нужно, или адекватно воспринять несовершенство чужого кода, если он выполняет возложенные на него функции. Желание переписать все с нуля, чтобы сделать «правильно», часто ведет к очень плохим последствиям."

Поддержу его своими несколько "необычными" цитатами:

http://18delphi.blogspot.ru/2013/09/blog-post_5343.html?showComment=1380310247097#c6038036058259213842

""Какими соображениями мотивировано такое порождение?"
-- был взят СУЩЕСТВУЮЩИЙ код. МНОГО раз написанный и переписанный. РАЗНЫМИ людьми и в разное время. В том или ином качестве. И отличающийся лишь "параметрами инстанцирования" и некоторыми условиями.

И этот код был ШАБЛОНИЗИРОВАН.

Никаких "новых велосипедов" не изобреталось. Лишь шаблонизировались старые.

Можно было конечно пойти по другому пути - тотально всё отрефакторить. Но это - дорого и не факт, что эффективнее.

Но нами были лишь шаблонизированы УЖЕ существующие решения. Конечная система об этом - "не узнала"."


http://18delphi.blogspot.ru/2013/09/blog-post_5343.html?showComment=1380314679101#c7561211855437726970

""Понимаете в чём дело... В классическом программировании, в сущности, рекомендуется один путь - рефакторинг, целью которого является устранение дублирования.
Это настолько привычная вещь, что многими людьми (и мною, в том числе) она принимается за аксиоматику."

-- именно так. И тут ВЫ ПРАВИЛЬНО всё написали. Именно.
Мимикрия под СУЩЕСТВУЮЩИЙ код. Чтобы "не разломать".

Некоторым этот путь кажется тупиковым. И с ними я как "чистый" программист - согласен. Но как практик - нет. Чаще прощё "залатать", чем переделывать архитектуру. Хотя я и не сторонник этого.

Но столько людей "латают", что это тоже - лучше шаблонизировать, нежели "латать на коленке"."

Только упаси вас Бог посчитать, что я ПРОТИВ рефакторинга. Я - ОЧЕНЬ даже за. И я - ЛЮБЛЮ им заниматься. И получаю от этого удовольствие. И всеми силами стараюсь делать так, чтобы коллеги осознавали его необходимость и проводили его. Но всё хорошо к месту и в правильное время.

"неумение относиться к коду без эмоций" - а вот это - я к СОЖАЛЕНИЮ - не умею.

пятница, 27 сентября 2013 г.

О сложности диаграмм...

http://18delphi.blogspot.ru/2013/09/blog-post_1303.html?showComment=1380312709199#c6777293502412066273

"И начнёте думать, как упростить это, если "это" представляет для Вас какую-либо ценность. Я не стану рассказывать о декомпозиции, о выделении родственных сущностей и формализации связей в предметной области - Вы опять скажете, что я Вас с кем-то путаю. Но это как раз тот путь, о котором Вы говорите, что его-де "никто не знает". - Знает, Александр. Знает."

Это - ОЧЕВИДНО и ЛЕЖИТ НА ПОВЕРХНОСТИ.

Просто нужен совсем "иной UML" И совсем "иной UML-редактор".

Собственно о чём мы с Максом и толкуем.

Это если за скобки опустить необходимость "разгребания конюшен.

"Я не стану рассказывать о декомпозиции, о выделении родственных сущностей и формализации связей в предметной области" - да - не стоит.

Не то чтобы не стоит. Но я знаю про них. И регулярно занимаюсь этим.

Приведённая диаграмма была ещё "хуже".

"формализации связей в предметной области" - вот тут к сожалению - я далеко не всегда властен. Тут есть "заказчики".

И любая формализация может выйти "боком".

Я кстати писал немного об этом - http://18delphi.blogspot.ru/2013/09/blog-post_16.html

Формализация - она хороша. И я ОБЕИМИ руками за неё. Но! "Заказчики" - далеко не всегда её понимают.

Бывает - ДВЕ сущности - одинаковые. Вообще - мама родная не отличит. Но "Заказчики" хотят от них "совершенно разного поведения". По неформальным признакам. И тут - никакие формальные методы не работают.

Только если шаблонизация в терминах UML и UseCase'ов.

Offtopic. Мои личные ощущения от iOS7

Раньше все производители интерфейсов стремились к "реалистичности", теням, отражениям, 3D... а теперь все стремятся к минимализму, графичности и "вычурной" плоскости.. что Win 8, что iOS7... про Android - не знаю..

Ну с точки зрения программистов - оно и понятно. Так - проще. И ресурсов меньше жрёт.

Странно только, что Apple идёт "по стопам" MS. Или не по стопам?

Пример кода порождения стереотипов из других стереотипов

Для тех самых настроек (http://18delphi.blogspot.ru/2013/09/uml_5682.html):


// перекрытие базового стереотипа UseCase::Class
%f _DoSpell
//#UC START# *4E302A8B026Cfor4E30ECBB004D*
 %S%[inherited]\

 <{}{%LS=VCMUseCaseRealization}\
 %L<{}{%GS=VCMFormsPack}\
 %1%f_SpellToMod(%G,%G%P,%S)\
 >\
 >\

 <{}{%LS=SettingsHolder}\
 %S%f_SpellToMod(%L,%L%P,%L)\
 >\

%f _SpellToMod
 %{SELF}%f_set_var(HasNestedSettings,"false")\

 [{"%S{CodeName}"!=""&%Cx=true}\
 %f_set_var(SELF,S)\
 %{SELF}%f_set_var(VALUES_COUNT,"%{SELF}<{}{%CS=UseCaseSettingValue&"%C{CodeName}"!=""}{C}>")\
 [{%{SELF}%{VALUES_COUNT}N!=0}\

 [{%{SELF}%{VALUES_COUNT}N=2}{\
 %{SELF}%f_set_var(IsBool,"false")\
 }\
 %{SELF}%f_set_var(IsBool,"true")\
 ]\

 %{SELF}<{}{%CS=UseCaseSettingValue&"%C{CodeName}"!=""}%C<{}{%CS=UseCaseSetting&"%C{CodeName}"!=""}%{SELF}%f_set_var(HasNestedSettings,"true")>>\

 [{"%{BOOLEAN}N"=""}%f_find_element(46A603BB0391,BOOLEAN)]\
 [{"%{Integer}N"=""}%f_find_element(46A606AC03B2,Integer)]\
 [{"%{TddComboBoxConfigItem}N"=""}%f_find_element(TddComboBoxConfigItem,TddComboBoxConfigItem)]\
 [{"%{TddAppConfiguration}N"=""}%f_find_element(TddAppConfiguration,TddAppConfiguration)]\
 [{"%{TddBaseConfigItem}N"=""}%f_find_element(TddBaseConfigItem,TddBaseConfigItem)]\
 [{"%{TddConfigValue}N"=""}%f_find_element(TddConfigValue,TddConfigValue)]\
 [{"%{l3FillChar}N"=""}%f_find_element(l3FillChar,l3FillChar)]\
 [{"%{Tafw}N"=""}%f_find_element(Tafw,Tafw)]\

 %f_set_var(SELF,S)\

 %f_set_var(MOD,1)\
 %f_set_var(PAR,2)\

 %{PAR}%f_add_class(%{SELF}U_%{MOD}U_Res,UtilityPack,%f_N(%{MOD})%{SELF}{CodeName}SettingRes,Pack_Inst)\

 [{%{SELF}%{IsBool}N=true}{\
 %{Pack_Inst}%f_add_class(%{Pack_Inst}U_Enum,Enum,%{SELF}{CodeName}Enum,Enum_Inst)\
 %{Enum_Inst}%f_set_documentation(Ключи для настройки "%{SELF}N")\

 %{SELF}<{}{%CS=UseCaseSettingValue&"%C{CodeName}"!=""}\
 %{Enum_Inst}%f_add_attribute(%{SELF}U_%{MOD}U_%CU_SettingValue_Key,,\
 KEY_%{SELF}{CodeName}_%C{CodeName},\
 Attr_Inst)\
 %{Attr_Inst}%f_set_documentation(%CN)\

 >\
 }\
 ]\

 [{%{MOD}S=SettingsHolder}{\
 %f_set_var(METHOD,"FactoryMethod")\
 %f_set_var(METHODN,"%f_N(%{MOD})")\
 }\
 %f_set_var(METHOD,"method")\
 %f_set_var(METHODN,"")\
 ]\

 [{%{SELF}%{HasNestedSettings}N=true}{\
 %{MOD}%f_add_class(%{SELF}U_%{MOD}U_FM,%{METHOD}N,%{METHODN}N%{SELF}{CodeName}Setting,FM_Inst)\
 %{FM_Inst}%f_set_visibility_type(PublicAccess)\
 %{FM_Inst}%f_set_abstraction_type(final)\
 %{FM_Inst}%f_set_documentation(Метод для получения значения настройки "%{SELF}N")\

 %{FM_Inst}%f_make_accessable(%{SELF}U)\
 %{FM_Inst}%f_add_realized(%{SELF}U)\

 %{FM_Inst}%f_make_accessable(%{Tafw}U)\
 %{FM_Inst}%f_add_dependency(%{FM_Inst}U_%{Tafw}U_uses,%{Tafw}U,uses,,USES_Inst)\
 %{FM_Inst}%f_set_up(NO_FACTORY_BRACKECTS,true)\
 %{FM_Inst}%f_set_up(is static,true)\
 %{FM_Inst}%f_add_operation(%{FM_Inst}U_DoIt,,DoIt (): \
 [{%{SELF}%{IsBool}N=true}{\
 %{Integer}U}\
 %{BOOLEAN}U\
 ]\
 ,Op_Instance)\
 %{Op_Instance}%f_set_documentation(%{FM_Inst}D)\
 %{FM_Inst}%f_set_uc_content(intf.pas,_%f_pas_MethodOwnerID(%{FM_Inst},%{MOD})_var,\
  {-}\
 )\
 %{FM_Inst}%f_set_uc_content(intf.pas,_%f_pas_MethodOwnerID(%{FM_Inst},%{MOD})_impl,\
  if (afw.Settings = nil) then
   Result := dv_%f_N(%{MOD})_%{SELF}{CodeName}
  else
   Result := afw.Settings.\
 [{%{SELF}%{IsBool}N=true}{\
 LoadInteger\
 }\
 LoadBoolean\
 ]\
 (pi_%f_N(%{MOD})_%{SELF}{CodeName}, dv_%f_N(%{MOD})_%{SELF}{CodeName});\
 )\

 %{MOD}%f_add_class(%{SELF}U_%{MOD}U_FM_W,%{METHOD}N,Write%{METHODN}N%{SELF}{CodeName}Setting,FM_Inst_W)\
 %{FM_Inst_W}%f_set_visibility_type(PublicAccess)\
 %{FM_Inst_W}%f_set_abstraction_type(final)\
 %{FM_Inst_W}%f_set_documentation(Метод для записи значения настройки "%{SELF}N")\

 %{FM_Inst_W}%f_make_accessable(%{SELF}U)\
 %{FM_Inst_W}%f_add_realized(%{SELF}U)\

 %{FM_Inst_W}%f_make_accessable(%{Tafw}U)\
 %{FM_Inst_W}%f_add_dependency(%{FM_Inst_W}U_%{Tafw}U_uses,%{Tafw}U,uses,,USES_Inst)\
 %{FM_Inst_W}%f_set_up(NO_FACTORY_BRACKECTS,true)\
 %{FM_Inst_W}%f_set_up(is static,true)\
 %{FM_Inst_W}%f_add_operation(%{FM_Inst_W}U_DoIt,,DoIt (aValue : \
 [{%{SELF}%{IsBool}N=true}{\
 %{Integer}U}\
 %{BOOLEAN}U\
 ]\
 ),Op_Instance)\
 %{Op_Instance}%f_set_documentation(%{FM_Inst_W}D)\
 %{FM_Inst_W}%f_set_uc_content(intf.pas,_%f_pas_MethodOwnerID(%{FM_Inst_W},%{MOD})_var,\
  {-}\
 )\
 %{FM_Inst_W}%f_set_uc_content(intf.pas,_%f_pas_MethodOwnerID(%{FM_Inst_W},%{MOD})_impl,\
  if (afw.Settings \<\> nil) then
   afw.Settings.\
 [{%{SELF}%{IsBool}N=true}{\
 SaveInteger\
 }\
 SaveBoolean\
 ]\
 (pi_%f_N(%{MOD})_%{SELF}{CodeName}, aValue);\
 )\
 }\

 %f_set_var(FIRST,"true")\
 %{SELF}<{}{%CS=UseCaseSettingValue&"%C{CodeName}"!=""}\
 %{MOD}%f_add_class(%{SELF}U_%{MOD}U_%CU_FM,%{METHOD}N,%{METHODN}N%{SELF}{CodeName}%C{CodeName}Setting,FM_Inst)\
 %{FM_Inst}%f_set_visibility_type(PublicAccess)\
 %{FM_Inst}%f_set_abstraction_type(final)\
 %{FM_Inst}%f_set_documentation(Метод для получения значения настройки "%{SELF}N"."%CN")\

 %{FM_Inst}%f_make_accessable(%{SELF}U)\
 %{FM_Inst}%f_add_realized(%{SELF}U)\

 %{FM_Inst}%f_make_accessable(%{Tafw}U)\
 %{FM_Inst}%f_add_dependency(%{FM_Inst}U_%{Tafw}U_uses,%{Tafw}U,uses,,USES_Inst)\
 %{FM_Inst}%f_set_up(NO_FACTORY_BRACKECTS,true)\
 %{FM_Inst}%f_set_up(is static,true)\
 %{FM_Inst}%f_add_operation(%{FM_Inst}U_DoIt,,DoIt (): \
 %{BOOLEAN}U\
 ,Op_Instance)\
 %{Op_Instance}%f_set_documentation(%{FM_Inst}D)\
 %{FM_Inst}%f_set_uc_content(intf.pas,_%f_pas_MethodOwnerID(%{FM_Inst},%{MOD})_var,\
  {-}\
 )\
 %{FM_Inst}%f_set_uc_content(intf.pas,_%f_pas_MethodOwnerID(%{FM_Inst},%{MOD})_impl,\
  if (afw.Settings = nil) then
   Result := [{%{FIRST}N=true}{false}true]
  else
   Result := afw.Settings.\
 LoadBoolean\
 (pi_%f_N(%{MOD})_%{SELF}{CodeName}_%C{CodeName}, [{%{FIRST}N=true}{false}true]);\
 )\
 %f_set_var(FIRST,"false")\
 >\

 ]\

 %{MOD}%f_add_dependency(%{MOD}U_%{Pack_Inst}U_uses,%{Pack_Inst}U,uses,,USES_Inst)\
 %{Pack_Inst}%f_set_documentation(Ресурсы для настройки "%{SELF}N")\
 %{Pack_Inst}%f_make_accessable(%{SELF}U)\
 %{Pack_Inst}%f_add_realized(%{SELF}U)\

 %{Pack_Inst}%f_add_class(%{Pack_Inst}U_Key,Constants,%{SELF}{CodeName}Key,Const_Inst)\
 %{Const_Inst}%f_add_attribute(%{Const_Inst}U_Key,,\
 pi_%f_N(%{MOD})_%{SELF}{CodeName} = '%{MOD}D/%{SELF}N',\
 Attr_Inst)\
 %{Attr_Inst}%f_set_documentation(Идентификатор настройки "%{SELF}N")\

 [{%{SELF}%{HasNestedSettings}N=true}\
 %{SELF}<{}{%CS=UseCaseSettingValue&"%C{CodeName}"!=""}\
 %{Const_Inst}%f_add_attribute(%{Const_Inst}U_%CU_Key,,\
 pi_%f_N(%{MOD})_%{SELF}{CodeName}_%C{CodeName} = '%{MOD}D/%{SELF}N/%CN',\
 Attr_Inst)\
 %{Attr_Inst}%f_set_documentation(Идентификатор настройки "%{SELF}N"."%CN")\
 >\
 ]\

 [{%{SELF}%{HasNestedSettings}N!=true}\
 %{Const_Inst}%f_add_attribute(%{Const_Inst}U_DefaultValue,,\
 dv_%f_N(%{MOD})_%{SELF}{CodeName} = \
 [{%{SELF}%{IsBool}N=true}{\
 0\
 }\
 false\
 ]\
 ,\
 Attr_Inst)\
 %{Attr_Inst}%f_set_documentation(Значение по-умолчанию настройки "%{SELF}N")\
 ]\

 %{Pack_Inst}%f_add_class(%{Pack_Inst}U_Name,LocalConst,%{SELF}{CodeName}Name,Local_Inst)\
 %{Local_Inst}%f_set_documentation(Имя настройки "%{SELF}N")\
 %{Local_Inst}%f_set_visibility_type(PublicAccess)\
 %{Local_Inst}%f_add_attribute(%{SELF}U_%{MOD}U_%SU_SettingName,,\
 %{SELF}{CodeName} = '%{SELF}N',\
 Attr_Inst)\
 %{Attr_Inst}%f_set_documentation(%{SELF}N)\

 %{Pack_Inst}%f_add_class(%{Pack_Inst}U_Values,LocalConst,%{SELF}{CodeName}Values,Local_Inst)\
 %{Local_Inst}%f_set_documentation(Значения для настройки "%{SELF}N")\
 %{Local_Inst}%f_set_visibility_type(PublicAccess)\

 [{%{SELF}%{HasNestedSettings}N!=true}\
 [{%{SELF}%{IsBool}N=true}{\
 %{Local_Inst}%f_add_inheritable(%{Enum_Inst}U)\
 }\
 %{Local_Inst}%f_add_inheritable(%{BOOLEAN}U)\
 ]\

 %{Local_Inst}%f_set_up(Need mapping support,true)\
 %{Local_Inst}%f_set_up(Need map interface,true)\
 ]\

 %{Local_Inst}%f_set_up(elements prefix,%{SELF}{CodeName})\

 %{SELF}<{}{%CS=UseCaseSettingValue&"%C{CodeName}"!=""}\
 %{Local_Inst}%f_add_attribute(%{SELF}U_%{MOD}U_%CU_SettingValue,,\
 %C{CodeName} = '%CN',\
 Attr_Inst)\
 %{Attr_Inst}%f_set_documentation(%CN)\
 >\

 [{%{SELF}%{HasNestedSettings}N!=true}\
 %f_set_var(Hack,"false")\
 [{%{PAR}N=StyleTable}\
 %f_set_var(Hack,"true")\
 %f_set_var(PAR,{TddComboBoxConfigItem}%P%P)\
 ]\
 %{PAR}%f_add_class(%{SELF}U_%{MOD}U_AppConfig,SimpleClass,Tst%f_N(%{MOD})%{SELF}{CodeName}Item,Class_Inst)\
 %{Class_Inst}%f_set_documentation(Визуализатор для настройки "%{SELF}N")\
 %{Class_Inst}%f_make_accessable(%{SELF}U)\
 %{Class_Inst}%f_add_realized(%{SELF}U)\
 %{Class_Inst}%f_make_accessable(%{Pack_Inst}U)\
 %{Class_Inst}%f_add_dependency(%{Class_Inst}U_%{Pack_Inst}U_uses,%{Pack_Inst}U,uses,,USES_Inst)\

 %{Class_Inst}%f_make_accessable(%{TddComboBoxConfigItem}U)\
 %{Class_Inst}%f_make_accessable(%{l3FillChar}U)\
 %{Class_Inst}%f_make_accessable(%{TddConfigValue}U)\
 %{Class_Inst}%f_add_inheritable(%{TddComboBoxConfigItem}U)\
 %{Class_Inst}%f_add_dependency(%{Class_Inst}U_%{l3FillChar}U_uses,%{l3FillChar}U,uses,,USES_Inst)\
 %{Class_Inst}%f_add_dependency(%{Class_Inst}U_%{TddConfigValue}U_uses,%{TddConfigValue}U,uses,,USES_Inst)\
 %{Class_Inst}%f_add_operation(%{Class_Inst}U_Create,ctor,Create (aMasterItem : %{TddBaseConfigItem}U = nil),Op_Instance)\
 %{Op_Instance}%f_set_uc_content(intf.pas,_%f_pas_MethodOwnerID(%{Op_Instance},%{Class_Inst})_var,\
 var
  l_Value : TddConfigValue;\
 )\
 %{Op_Instance}%f_set_uc_content(intf.pas,_%f_pas_MethodOwnerID(%{Op_Instance},%{Class_Inst})_impl,\
  l3FillChar(l_Value, SizeOf(l_Value));
  l_Value.Kind := \
 [{%{SELF}%{IsBool}N=true}{\
 dd_vkInteger\
 }\
 dd_vkBoolean\
 ]\
 ;
  [{%{SELF}%{IsBool}N=true}{\
 l_Value.AsInteger\
 }\
 l_Value.AsBoolean\
 ]\
  := dv_%f_N(%{MOD})_%{SELF}{CodeName};
  inherited Create(pi_%f_N(%{MOD})_%{SELF}{CodeName},
   str_%{SELF}{CodeName}.AsStr,
   l_Value, T%{SELF}{CodeName}ValuesMapImpl.Make, aMasterItem);\
 )\

 [{%{Hack}N!=true}\
 %{MOD}%f_add_dependency(%{MOD}U_%{Class_Inst}U_uses,%{Class_Inst}U,uses,,USES_Inst)\
 ]\
 ]\

 ]\
 ]\

 [{%S%{HasNestedSettings}N=true}\

 %f_clear_list(CHILDREN_TO_SPELL)\

 <{}{%CS=UseCaseSettingValue&"%C{CodeName}"!=""}%C<{}{%CS=UseCaseSetting&"%C{CodeName}"!=""}\

 %f_set_var(THIS_UCMOD,3)\

 %f_set_var(REALIZE_COUNT,"%C<{}{%LU=%{THIS_UCMOD}U}{%LC}>")\

 [{%{REALIZE_COUNT}N=0}\

 %f_add_to_list(CHILDREN_TO_SPELL,C)\
 ]\

 >\
 >\

 %f_set_var(THIS_MOD,1)\
 %f_set_var(THIS_PAR,2)\
 %f_set_var(THIS_UCMOD,3)\

 %{CHILDREN_TO_SPELL}<{}{}{%C}\
 %C%f_SpellToMod(%{THIS_MOD},%{THIS_PAR},%{THIS_UCMOD})\
 >\

 %f_clear_list(CHILDREN_TO_SPELL)\

 ]
//#UC END# *4E302A8B026Cfor4E30ECBB004D*

Немного расшифрую:
%{Class_Inst}%f_make_accessable(%{TddComboBoxConfigItem}U)\
%{Class_Inst}%f_make_accessable(%{l3FillChar}U)\
%{Class_Inst}%f_make_accessable(%{TddConfigValue}U)\
%{Class_Inst}%f_add_inheritable(%{TddComboBoxConfigItem}U)\

=>

Class_Inst.make_accessable(TddComboBoxConfigItem);
Class_Inst.make_accessable(l3FillChar);
Class_Inst.make_accessable(TddConfigValue);
Class_Inst.add_inheritable(TddComboBoxConfigItem);
%f _DoSpell
//#UC START# *4E302A8B026Cfor4E30ECBB004D*
 %S%[inherited]\
 
 <{}{%LS=VCMUseCaseRealization}\
 %L<{}{%GS=VCMFormsPack}\
 %1%f_SpellToMod(%G,%G%P,%S)\
 >\
 >\
 
 <{}{%LS=SettingsHolder}\
 %S%f_SpellToMod(%L,%L%P,%L)\
 >\

=>

UseCaseSetting.DoSpell;
begin
//#UC START# *4E302A8B026Cfor4E30ECBB004D*
 inherited;
 
 for (L in Self.Realizers) do
  if L.StereotypeIs(VCMUseCaseRealization) then
   for (G in L.Ancestors) do
    if G.StereotypeIs(VCMFormsPack) then
     self.SpellToMod(G, G.Parent, L);
 
 for (L in Self.Realizers) do
  if L.StereotypeIs(SettingsHolder) then
   self.SpellToMod(L, L.Parent, L);
end;
%{PAR}%f_add_class(%{SELF}U_%{MOD}U_AppConfig,SimpleClass,Tst%f_N(%{MOD})%{SELF}{CodeName}Item,Class_Inst)

=>

Class_Inst := PAR.add_class(self.UID + MOD.UID + '_AppConfig', SimpleClass, 'Tst' + Name(MOD) + self.CodeName + 'Item');
 [{"%{TddConfigValue}N"=""}%f_find_element(TddConfigValue,TddConfigValue)]\
 [{"%{l3FillChar}N"=""}%f_find_element(l3FillChar,l3FillChar)]\

=>

 if not Assigned(TddConfigValue) then TddConfigValue := find_element('TddConfigValue');
 if not Assigned(l3FillChar) then l3FillChar := find_element('l3FillChar');

Ссылка. Блог моего школьного товарища

http://pogankin.blogspot.ru/2008/07/rpm.html

Там немного правда пока. И про Linux. Я в этом - ничего не понимаю. Но вдруг кому окажется полезным.

Ссылка. Про стереотипы в UML

Об "общей" модели системы

У нас система - далеко не самая сложная. Но - сложная.

Диаграмма пакетов верхнего уровня выглядит примерно (примерно - потому, что там внизу ещё примерно экран не поместился) так:



Я не знаю пока делать с этой диаграммой. Посему об "общей" модели - стоит забыть.

Если кому-то кажется, что я привёл эту диаграмму "по глупости" и "с кандачка", то он - СИЛЬНО заблуждается. Я привёл эту диаграмму ОСОЗНАННО.

Сложность и запутанность этой диаграммы ПОКАЗЫВАЕТ не сложность и запутанность UML. Она показывает РЕАЛЬНУЮ сложность РЕАЛЬНОЙ системы.

До тех пор пока эта диаграмма не была нарисована - о сложности системы - "никто не подозревал".

И таких диаграмм была уже МАССА нарисована. И на основе их анализа было проведено упрощение системы и её тотальный рефакторинг.

Тех диаграмм, что внутри.

Это же - ОБЩАЯ диаграмма всего процесса. ВСЕГО предприятия если хотите.

Да - она сложная и запутанная. Но это означает, что процесс - сложный. И что его надо упрощать. Как? Пока - никто не знает.

Если найдутся умники "говорящие фуу как сложно" - один совет - нарисуйте общую диаграмму ВАШЕГО предприятия. И посмотрите на неё. И потом только - высказывайте оценки.

И эта диаграмма НЕ бесполезна. Бесполезную работу я В ПРИНЦИПЕ стараюсь не делать. Чего и всем желаю.

ARIS

Почитал вот это - http://ru.wikipedia.org/wiki/ARIS

Ну - не "задело струны".

Возможно - не понял.

"Для описания бизнес-процессов предлагается использовать около 80 типов моделей"

--- что-то - МНОГО, если речь действительно о ТИПАХ, а не ЭКЗЕМПЛЯРАХ.

И вот ещё о модели, мета-модели и шаблонах

Исходное обсуждение тут - http://18delphi.blogspot.ru/2013/09/uml_5682.html?showComment=1380234644934#c5618414022323679452

"Иными словами, как, имея диаграмму и шаблоны"

-- давайте только точки над i поставим. Шаблоны (мета-модель) - первичны. Модель - вторична.

"конечному" программисту - "не надо думать" о шаблонах (мета-модели). Они  есть. Априори. Они для него - аксиоматика.

Вот если "реальная жизнь" - НЕ УКЛАДЫВАЕТСЯ в аксиоматику - это - проблема. Значит - надо менять мета-модель. Если есть "хорошая" мотивация для этого.

Offtopic. Что сказать... Нашёл достойного собеседника

http://18delphi.blogspot.ru/2013/09/uml_5682.html?showComment=1380234644934#c5618414022323679452

Вот когда без "взгляда сверху" и менторства (которых в данном куске комментариев нет), то - очень достойный собеседник.

Вопросы - по делу. Мне кажется - проработанные и "выстраданные".

Ещё о мета-моделях

Основное обсуждение тут - http://18delphi.blogspot.ru/2013/09/uml_5682.html?showComment=1380235293530#c1496389099845931060

Там по-моему я написал ВАЖНУЮ мысль:

"В шаблонах записан не код. А ПРИНЦИПЫ генерации кода. Это ВАЖНО.

Более того - шаблоны - могут опираться на конкретные модели, построенные на этих шаблонах.

Ну как "раскрутка компилятора".

Есть мета модель (1). На ней нарисована модель (1). Когда делаем мета-модель (2) мы можем опираться на модель (1). В итоге получаем мета-модель (2) на которой делаем модель (2). И далее - по индукции."

Ещё немножко банального о "нашем UML" и мета-модели

Вот тут я немного описал суть - http://18delphi.blogspot.ru/2013/09/blog-post_4427.html

Но!

Есть ещё один аспект. Мелкий. "Банальный". Но КРАЙНЕ полезный.

На мета-модели можно ещё определять способ визуализации элементов модели.

Например:
View - фиолетовые.
SimpleClass - салатовые.
MixIn- зелёные.
Interface и Facet - "примерно синие".
PureMixIn - голубые.
Exception - красные.

Ну и т.п.

Мелко. "Банально". Но чертовски удобно визуально. Привыкаешь и начинаешь "читать диаграмму подкоркой мозга".

Меня лично - прикололо

http://roman.yankovsky.me/?p=942

Хотя я задаюсь теми же вопросами, что и Роман.

Ещё наш кодогенератор умеет порождать "виртуальные" элементы из реальных, нарисованных пользователем

Например.

Рисую я на модели элемент со стереотипом "LocalConst". Это коробка с локализованными строками.

На самом деле она порождает собой другую коробку - "Consts" ну и другие сопутствующие элементы, типа "ключей для локализации", а также другую подобную инфраструктуру.

И эти "порождаемые" элементы - делает кодогенератор. И они доступны пользователю, как если бы он их нарисовал бы руками.

А если "родительские" элементы удаляются или переезжают в другое место, то и "порождаемые элементы" испытывают соответствующую трансформацию.

Или вот есть стереотип "TestTarget". Он указывает на "Target". На приложение, для которого делаются эти тесты.

Так вот - можно рисовать руками все внутренности TestTarget'а. Если требуется так скажем "fine tuning".

А можно оставить его пустым. Просто поставив соответствующую стрелку на модели.

И все "кишки" данного TestTarget'а кодогенератор породит САМ. Так как указано в мета-модели.

С одной стороны и гибкость - можно всё настроить "руками", а с другой стороны - "стандартные шаблонные решения".

Ну или банально. Есть стереотипа "Form". Так вот по-умолчанию ему можно не ставить связь наследования к TForm из стандартной библиотеки. Кодогенератор её сам подставит. При этом - будет возможность переопределять соответствующие методы базового класса. А если нас такое наследование не устраивает - мы можем поставить ДРУГОЕ, но уже - руками.

Вот как-то так.

Про LocalConst пример вот такой:
// перекрытие базового стереотипа Delphi интерфейсы и реализация::MDAGenerator
%f _DoSpell
//#UC START# *4B2A19E3038Bfor4B386A11030E*
 %S%[inherited]\
 [{"%{Tl3StringIDEx}N"=""}%f_find_element(Tl3StringIDEx,Tl3StringIDEx)]\
 [{"%{Tl3MessageID}N"=""}%f_find_element(Tl3MessageID,Tl3MessageID)]\
 [{"%{Dialogs}N"=""}%f_find_element(4AB0EE02004E,Dialogs)]\

// <{}{%C#f_IsMessage()=true}\
// %C<{}{%C#f_IsChoices()=true}\
// %C%f_DoSpell()>\
// >\

 %f_set_var(FOUND,"false")\
 %f_set_var(PARENT,"")\
 <{}{%P#f_IsClassBase()=true|%P#f_IsUtilityPack()=true}{%P}\
 [{%{FOUND}N=false}\
 %f_set_var(FOUND,"true")\
 %f_set_var(PARENT,P)\
 ]\
 >\

 %{PARENT}%f_make_accessable(%{Tl3StringIDEx}U)\
 %{PARENT}%f_make_accessable(%{Tl3MessageID}U)\
 [{%{PARENT}C=Class}\
 %{PARENT}%f_add_dependency(%SU_%{Tl3StringIDEx}U_uses,%{Tl3StringIDEx}U,uses,,USES_Inst)\
 %{PARENT}%f_add_dependency(%SU_%{Tl3MessageID}U_uses,%{Tl3MessageID}U,uses,,USES_Inst)\
 ]\
 %{PARENT}%f_add_class(%SU_%{Tl3StringIDEx}U_LCImpl,Constants,LCImpl[{%S#f_IsChoices()=true}_%PN_]%SN,LocalConst_Inst)\
 %{LocalConst_Inst}%f_set_visibility_type(%SV)\

 [{%S#f_IsChoices()=true}{\
 %{LocalConst_Inst}%f_set_documentation(Локализуемые строки %SN)\
 }\
 %{LocalConst_Inst}%f_set_documentation(Варианты выбора для диалога %PN)\
 ]\

 %{LocalConst_Inst}%f_set_up(TreatAsVars,true)\
// - шаманство, чтобы превратить константы в переменные
 %{LocalConst_Inst}%f_set_up(ifdef,%S{ifdef})\
 %{LocalConst_Inst}%f_set_up(ifndef,%S{ifndef})\
 %{LocalConst_Inst}%f_set_up(children prefix,%S{children prefix})\
 %{LocalConst_Inst}%f_set_up(elements prefix,str[_%S{elements prefix}])\

 %f_set_var(WAS_USES_TO_DIALOGS,"false")\

 <{}{}{%C}\

 [{%C#f_IsMessage()=true}{\
 %f_set_var(CONST_TYPE,{Tl3StringIDEx})\
 }\
 [{%{WAS_USES_TO_DIALOGS}N!=true}\
 %{PARENT}%f_make_accessable(%{Dialogs}U)\
 %f_set_var(WAS_USES_TO_DIALOGS,"true")\
 %{PARENT}%f_add_dependency(%SU_%{Tl3MessageID}U_uses_Dialogs,%{Dialogs}U,uses,,USES_Inst)\
 ]\
 %f_set_var(CONST_TYPE,{Tl3MessageID})\
 ]\

 %{LocalConst_Inst}%f_add_attribute(%{LocalConst_Inst}U_%CU_Impl,,%C#f_AdditionalPrefix()%f_N(%C) :\
  %{CONST_TYPE}U = (rS : -1; rLocalized : false;\
  rKey : '%C%f_pas_Prefix()%C#f_AdditionalPrefix()%f_N(%C)';\
  rValue : \

 [{"%CD"=""|%C#f_CanUseDocAsValue()!=true}{\
//                 ^ - ибо UserType'ы неправильно выливаются
 %CD\
 }\
 [{"%C{Value}"=""}{\
 %C{Value}\
 }\
 %C%VN\
 ]\
 ]\

 ),Attr_Inst)\

 %{Attr_Inst}%f_set_visibility_type(%CV)\
 %{Attr_Inst}%f_set_documentation([{}{%C{Value}}[{}{%C%VN}%CD]])\
 %{Attr_Inst}%f_set_up(ifdef,%C{ifdef})\
 %{Attr_Inst}%f_set_up(ifndef,%C{ifndef})\
 %{PARENT}%f_add_operation(%CU_Init,ini,Init_%f_N(%{Attr_Inst}) (),Op_Instance)\
 %{Op_Instance}%f_set_documentation(Инициализация %{Attr_Inst}%f_pas_Prefix()%f_N(%{Attr_Inst}))\
 %{Op_Instance}%f_set_abstraction_type(final)\
 %{Op_Instance}%f_set_visibility_type(PrivateAccess)\

 [{"%S{ifdef}"!=""}{\
 %f_set_var(IFDEF_VALUE,"%C{ifdef}")\
 }\
 [{"%C{ifdef}"!=""}{\
 %f_set_var(IFDEF_VALUE,"%S{ifdef}")\
 }\
 %f_set_var(IFDEF_VALUE,"%S{ifdef},%C{ifdef}")\
 ]\
 ]\

 %{Op_Instance}%f_set_up(ifdef,%{IFDEF_VALUE}N)\

 [{"%S{ifndef}"!=""}{\
 %f_set_var(IFDEF_VALUE,"%C{ifndef}")\
 }\
 [{"%C{ifndef}"!=""}{\
 %f_set_var(IFDEF_VALUE,"%S{ifndef}")\
 }\
 %f_set_var(IFDEF_VALUE,"%S{ifndef},%C{ifndef}")\
 ]\
 ]\

 %{Op_Instance}%f_set_up(ifndef,%{IFDEF_VALUE}N)\
 %{Op_Instance}%f_set_uc_content(intf.pas,,\
  %{Attr_Inst}%f_pas_Prefix()%f_N(%{Attr_Inst}).Init;\
 %C%f_AdditionalInitCode("%{Attr_Inst}%f_pas_Prefix()%f_N(%{Attr_Inst})")\
 )\
 >\

 [{%Gx=true}\
 %{PARENT}%f_add_class(%SU_%{CONST_TYPE}U_LCConstArr,ConstantArray,%SNMap,LocalConstArray_Inst)\
 [{"%{Pl3StringIDEx}N"=""}%f_find_element(Pl3StringIDEx,Pl3StringIDEx)]\
 %{LocalConstArray_Inst}%f_add_inheritable(%{Pl3StringIDEx}U)\
 %{LocalConstArray_Inst}%f_add_realized(%GU)\
 %{LocalConstArray_Inst}%f_set_up(ifdef,%S{ifdef})\
 %{LocalConstArray_Inst}%f_set_up(ifndef,%S{ifndef})\
 %{LocalConstArray_Inst}%f_set_visibility_type(%SV)\
 %{LocalConstArray_Inst}%f_set_documentation(Карта преобразования локализованных строк %SN)\
 %{LocalConstArray_Inst}%f_set_up(Need mapping support,%S{Need mapping support})\
 %{LocalConstArray_Inst}%f_set_up(Need map interface,%S{Need map interface})\

 %{LocalConst_Inst}<{}{}{%C}\
 %{LocalConstArray_Inst}%f_add_attribute(%CU_Arr,,%f_N(%C) :\
  %{Pl3StringIDEx}U = %{LocalConst_Inst}N::%CN,Attr_Inst)\
 >\

 ]

//#UC END# *4B2A19E3038Bfor4B386A11030E*

четверг, 26 сентября 2013 г.

Коротко. Про языки с duck-typing

Может конечно я плохой программист (что вряд ли), но у меня создалось впечатление, что языки с duck-typing гораздо меньше прощают ошибки проектирования, нежели, чем строго типизированные языки. И никуда от этого не деться. И кстати именно языкам с duck-typing "модели" - нужнее.

Похожее - http://18delphi.blogspot.ru/2013/09/blog-post_23.html

О модели и мета-модели

На самом деле всё начинается у нас не с модели. А с мета-модели.

Мета-модель описывает то из чего могут состоять конечные клиентские модели.

Она описывает какие элементы могут туда входить и какие у них взаимоотношения.

Например, есть стереотип пакета "Interfaces", так вот на мета-модели - он определён и более того в ней описано, что в "Interfaces" могут входить только элементы со стереотипом "Interface" ну или что-то "подобное", а также - "простые типы".

Или есть "Views" - туда могут входить только "View" или что-то подобное.

Также мета-модель контролирует СВЯЗИ между элементами, такие как dependency, realization и generalization. А также - association.

На "оба конца" связей можно вводить ограничения на уровне мета-модели.

Например можно сказать, что Controller "имеет право" знать про "View", но не наоборот. При этом ограничения дифференцируются по типам связей. Для каждого типа (и стереотипа) связей - бывают собственные ограничения.

Таким образом UML и мета-модель - служат некоторого рода тьютором. Хотя "выстрелить себе в ногу" - конечно никто не запретит.

Вот вкратце - "зачем UML".

P.S. А есть ещё мета-мета-модель - в терминах которой описывается естественно мета-модель. В мета-мета-мета-модели - необходимости пока не было :-)

Ещё одна "штука", которую умеет наш кодогенератор

Приведу один пример. Он не единственный из этой области. Но самый показательный.

На многих экземплярах стереотипов модели можно выставить свойство Singleton.

И в итоге генератор ориентируюсь на него сгенерирует "шаблонное решение", для реализации именно этого "класса" в виде singleton'а.

При этом он учтёт особенности стереотипа и или вставит "примесное наследование", либо развернёт шаблон, либо напрямую включит соответствующий код в тело целевого класса.

И такого - много. Например тот же Refcounted.

О "велосипедах"...

Я работаю 17-ть лет над примерно "одним и тем же проектом". (А если говорить узко про "текстовый редактор" и обработку текстов - то и все 25-ть).

Посему - для меня "время остановилось".

В некотором смысле мне как СПЕЦИАЛИСТУ - ПОВЕЗЛО.

Мне не пришлось "бегать с места на место" и делать "сиюминутные проекты".

Мне представилась возможность изучить процесс проектирования, программирования, анализа и вообще создания системы, что называется "под микроскопом".

Мы начинали давно. Если говорить про Delphi. То мы начинали с "обыденного" Delphi-программирования в RAD-стиле. Писали свои компоненты. Делали массу форм в дизайнере форм. Понаписали массу своих расширений и визардов на ToolaAPI. Пользовались и чужими.

Но в какой-то момент мы пришли к НЕОБХОДИМОСТИ создания СВОИХ "системных библиотек" и собственного фреймворка. Эволюционно.

А не потому, что "программистам так захотелось".

И мы не жалеем об этом. Собственный фреймворк дал очень много плюсов. Минусы - конечно тоже есть. Но куда уж без них.

Но! ПЛЮСЫ - значительно перевешивают.

Мы многим могли бы поделиться с почившим Borland'ом или существующей Embarcadero. Уж очень много "блох" мы выловили. Это - правда.

НА этом фреймворке (с той или иной долей его участия) - построено НЕ ОДНО приложение. И внутренние и внешние.

Основные разработки уходят вглубь 10-тилетней, а то и 15-тилетней давности.

Так что - решения - взвешенные, зрелые и обкатанные.

Совсем недавно (в течении последних 5-ти лет) мы сделали - "новое". Тесты.

Это не значит, что тестов у нас не было.

Очень и очень давно у нас существуют "ручные тесты" и их организация.

Также достаточно давно у нас существует автоматическое GUI-тестирование.

Но этим всем занимается ОТДЕЛЬНАЯ группа. У которой цели и задачи, а также мотивация - другие.

А вот в последние пять лет мы занимаемся тестами на "уровне программистов".

Начали мы с "unit-тестов" (я не зря это взял в кавычки). Да и не стоит их путать с "полномасштабным" TTD, а "закончили" - GUI-тестами. Но на "программистском" уровне.

Это не значит, что у нас существует дублирование функциональности групп (хотя в дублировании и "внутренней конкуренции" - я ничего плохого не вижу).

Нет. У нас две группы - гармонично дополняют друг друга. И каждая решает СВОИ задачи. Под СВОИ цели.

ТЕСТИРОВЩИКИ - тестируют "чёрный" ящик.
ПРОГРАММИСТЫ - "белый".

Тестировщики - проверяют прецеденты и требования.
Программисты - регресс, стабильность архитектуры и "внутреннюю красоту" кода.

ТЕСТИРОВЩИКИ - тестируют нагрузку.
ПРОГРАММИСТЫ - тестируют граничные условия.

ТЕСТИРОВЩИКИ - смотрят на систему "глазами пользователя".
ПРОГРАММИСТЫ - "глазами заказчика", который заинтересован в дальнейшем развитии системы.

ТЕСТИРОВЩИКИ - консерваторы в плане поведения системы.
ПРОГРАММИСТЫ - в плане её архитектуры.

ТЕСТИРОВЩИКИ - новаторы в плане удобства пользователей.
ПРОГРАММИСТЫ - в плане технологических решений.

Все мы вместе занимаемся не удовлетворением "своего интереса" (но куда же без этого, интерес - это хорошо), но обеспечением КАЧЕСТВА конечного продукта. В меру своих сил.

Посему - ещё раз - все наши разработки, о которых я пишу - это не совсем "велосипеды" и не прихоть. Это исторически - продиктовано.

P.S. Так как наши разработки достаточно "стары", я писал - 10-15 лет, то мы конечно очень немного пользуемся сторонними вещами, появившимися после этого. Про тот же FastMem - мы знаем. И про его возможности. Но у нас есть своя следилка за памятью и объектами. Не такая продвинутая как FastMem, но зато - родная и обкатанная. На FastMem - я конечно уже очень давно хочу посмотреть внимательно. Но времени всё не выдаётся. Это же касается и других "современных разработок".

среда, 25 сентября 2013 г.

Offtopic. Но не могу не отметить это

Сейчас у нас в организации подобралась очень хорошая команда программистов на Delphi.

Не то чтобы раньше были плохие команды. Отнюдь.

Но сейчас как-то особенно - "на одной волне".

Дай бог им всем здоровья, ума и процветания. И долгой совместной работы.

И что ещё умеет наш фреймфорк?

Он умеет сохранять/восстанавливать данные текущей формы и её контролов (прецедента) в стек истории.

Наподобие стека переходов в браузерах.

Форме и её компонентам (контролам) достаточно лишь поддержать интерфейс IvcmState с методами SaveState/LoadState. Но даже если они этого не делают, то фреймворк пытается "угадывать" их состояние. По RTTI.

Ну и "данные прецедента" и "данные области ввода" - сохраняются атоматически.

Это к вопросу - "зачем свои фреймворки"...

вторник, 24 сентября 2013 г.

Прислали отзыв

http://18delphi.blogspot.com/2013/09/blog-post_19.html


Такая политика развития языка высокого уровня - самая правильная!



http://18delphi.blogspot.com/2013/09/blog-post_19.html


Пишите книгу. Определенно! Выйдет хорошо, прям чувствую :-)

А ещё наш фреймворк умеет публиковать ВСЕ сущности описанные в нём в тестовый фреймворк

А ещё наш фреймворк умеет публиковать ВСЕ сущности описанные в нём в тестовый фреймворк.

И тестировщики "автоматом" получают ВСЕ ручки которые определены ВНУТРИ приложения. И имеют возможность использовать их точно так же как и разработчики.

Т.е. некоторая часть тестировщиков имеет возможность работать с системой как с "белым ящиком".

Учитывая то, что понятия определяемые на UML - автоматом генерируются не только в код, но и в тестовый фреймворк - разработчики даже "не знают", что они не только разрабатывают систему, но и "определяют ручки для тестировщиков".

Это к вопросу - "зачем собственные фреймфорки"... И "зачем велосипеды"...

И опять же - даже БЕЗ использования UML - возможность "лёгкой" публикации "ручек" - сохраняется...

А что ещё умеет наш фреймворк?

Похоже на это - http://18delphi.blogspot.ru/2013/09/blog-post_4490.html

Но несколько не о том...

У нас есть возможность определять "Диалоги подтверждений".

Типа "а вы хотите перезаписать этот файл".

С определением вопроса и набора выборов и определения реакции на эти выборы.

А также отдельно "галочки" типа "запомнить выбор и больше не показывать".

В итоге - клиентский код знает ТОЛЬКО про идентификаторы диалогов и работает примерно так.

if Ask(msg_WantToOverrideFile) then
 DoOverride
else
 DoNotOverride;

или:

Case Choose(msg_WantExit) of
 mrYes : DoExit;
 mrNo : DoNoExit;
 mrCancel : DoCancelExit;
 else : assert('Неожиданный выбор');
end;//Case Choose(msg_WantExit)

Понятно, что "банально" и "велосипед". Но я люблю - "банальности" и "велосипеды".

Что ещё умеет наш фреймворк?

А ещё он умеет "локализовывать" строки.

Вместо типа String мы используем TafwString. Который определяется как-то так:

type TvcmString = object
 rKey : String;
 rValue : String;
 ...
 function Value: String;
 ...
end;//TvcmString

var
 SomeString : TvcmString = (rKey : 'SomeStringKey'; rValue : 'Это значение строки');

...

initialization
 SomeString.Register;

в чём цимес?

А в том, что эти строки регистрируются в глобальном менеджере строк. Который умеет сохранять их (в некий аналог ini-файла) и читать "скопом". А также умеет "переводить". Клиенты используют ТОЛЬКО функцию Value. Соответственно - мы всегда можем локализовать наше приложение.

Таких решений - на просторах интернета - масса. Но мы его к себе инкорпорировали. Только и всего.

Ну и конечно это генерируется из UML. Для упрощения жизни.

Но можно и руками писать. Как и ВСЁ, что генерируется из UML.

И снова о собственных фремворках

Есть такая библиотека DevExpress. Она мне всегда нравилась.

Сделана - хорошо.

(Оговорюсь - мы НЕ используем её в своих разработках, но очень плотно на неё смотрели)

Но! Там есть одна БОЛЬШАЯ проблема.

Всё построено на TAction'ах. Которые ДОЛЖНЫ лежать в ОДНОЙ общей коробке на MainForm.

Ну так уж она устроена.

Всё бы ничего. Пока форм-клиентов - немного. Но как только их (считай прецедентов) становится много - это всё превращается в "ад и ужас".

Потому, что MainForm становится НЕОБХОДИМО знать про ТИПЫ конкретных клиентских форм, а зачастую и про их внутреннее устройство. И начинаются - "макароны" и ОЧЕНЬ сильное связывание.

Я долго думал над этим и в итоге родилась сначала идея, а потом и реализация собственного фреймворка.

Грубо говоря так - MainForm содержит ТОЛЬКО прототипы Action'ов. В этих прототипах определяются визуальные параметры Action'ов, типа Caption, Hint, ImageIndex и т.п.

И есть - клиентские формы, которые работают в контексте MainForm.

На клиентских формах определяются "объекты" - операции. Которые рассказывают фреймворку, что мол эта форма умеет выполнять ту или иную операцию. Вот мол её Execute, а вот Update.

Ну это грубо, но идея именно такова.

Далее когда клиентская форма выходит на сцену (создаётся или получает фокус), то её операции подвязываются к обработчикам существующих Action'ов (ну и на самом деле могут и создавать их). А обработчики "предыдущих форм" - отвязываются.

Таким образом - мы получили "конструктор"-lego. Приложение конструируется из клиентских форм. Про внутреннее устройство которых оно "не знает". И этих форм - может быть - "сколь угодно много" - связность системы от этого не повышается.

Более того примерно эта же практика распространена и на КОНТРОЛЫ из которых состоят клиентские формы. Контролы ТОЖЕ имеют возможность ЗАЯВЛЯТЬ о том, что они ПОДДЕРЖИВАЮТ те или иные операции, которые тоже связываются с Action'ами.

Вот как-то так...

Развитие идеи (нереализованное пока) c с использованием атрибутов описано тут -  http://roman.yankovsky.me/?p=896#comment-3024

А про другие аспекты нашего фреймворка я немного писал тут - http://18delphi.blogspot.ru/2013/08/mvc.html

Ну и ещё тут - http://18delphi.blogspot.ru/2013/09/blog-post_16.html

И ещё о собственных фреймворках

Тут не раз звучал вопрос - "а зачем собственные фреймворки?"

Нарисовался вот ещё такой пример.

Пусть есть объект VCL. Контрол.

Пусть у него есть обработчик OnClick.

Пусть в этом OnClick выполняются какие-то действия.

И всё хорошо. До тех пор пока в OnClick не делается что-то такое, что опосредованно приводит к убиению контрола. Например контрол вызывает смену прецедента, которая, в свою очередь, вызывает смену текущей формы с её убиванием.

Итак - новая форма появилась. Старая - убилась. Вместе с ней - убился и контрол.

А потом, о ужас! Начинается возврат по стеку. И в итоге попадаем в исходные метод контрола, который вызвал OnClick. А контрол - УЖЕ убит. И тут начинаются варианты - либо "повезёт" и просто так "проедемся по памяти", либо AV, либо abstract error ну и т.п.

Так вот наш фреймворк даёт гарантию, что такого не произойдёт. Ну если соблюдать нехитрые правила.

Грубо говоря - в коде контрола, который вызывает обработчик зовутся пары методов afw.KeepObject(Self)/afw.UnkeepObject(Self), а destroy и FreeInstance контролов устроены таким образом, что не разрушают объект внутри этих скобок. Только ВНЕ их.

Если находится ВНЕШНИЙ проблемный код, то эти скобки ставятся и в нём.


Нет, я конечно знаю про Release. Но этот метод мало что гарантирует. Достаточно где-нибудь внутри обработчика случиться вызову Application.ProcessMessages и сразу появляется ненулевая вероятность получить приложение в нестабильном состоянии.

У меня иногда создаётся ощущение, что ВСЕ МЫ (программисты) зорко охраняем свой "ореол таинственности".. Что мол "программирование - это творчество"... И вообще - закрытая сфера...

Навеяло - http://18delphi.blogspot.ru/2013/09/uml.html?showComment=1379694200923#c3612269234248412906

Простите.. Но у меня иногда создаётся ощущение, что ВСЕ МЫ (программисты) зорко охраняем свой "ореол таинственности".. Что мол "программирование - это творчество"... И вообще - закрытая сфера...

По-моему - НЕТ ничего там ни "таинственного", ни "творческого" (я писал уже об этом).. Ну НИЧЕМ - наша работа НЕ ОТЛИЧАЕТСЯ от работы токаря или слесаря.. Ну или конструктора... На разных уровнях...

По-моему - программирование на 80% состоит из "умения выполнять те или иные УСТОЯВШИЕСЯ правила"... не более того.. ну и 10% на творчество... ну и 10 - на всё остальное...

УСТОЯВШИЕСЯ правила это:
шаблоны проектирования, UML, RUP, стандартные алгоритмы, Agile, "best-practice", инструкции, регламентные работы..

далее - "добавить - по-вкусу"...

А иногда бывает - "ореол в квадрате"...

P.S. Я же "пытаюсь" двигаться в "обратном" направлении. Я ХОЧУ - "чертежи". И чтобы ВСЕ их ЧИТАЛИ и МНОГИЕ - рисовали. И не ТОЛЬКО - "программеры". Я не хочу "творчества" (его и так достаточно - когда неделю роешься в отладчике и ищешь "тривиальную" ошибку). Я хочу "die Ordnung muss sein".

Хочу "чертёж прокладки" (http://18delphi.blogspot.ru/2013/09/uml_19.html).

понедельник, 23 сентября 2013 г.

Было дело - я делал одну "штуку" для UML и кодогенерации

Не могу не рассказать...

Ни для кого не секрет, что практически ВСЕ приложения имеют те или иные пользовательские настройки.

Настройки обычно выглядят как "дополнительные требования" к ТЗ на прецедент.

И в ТЗ они описываются "обычно" примерно так - "название настройки" -> "список допустимых значений".

Далее программист берёт это и "кодирует".

Кодирует он примерно следующее:
1. Идентификатор настройки во внутренней базе настроек. Ключ-константа.
2. Пользовательское имя настройки - локализуемое значение.
3. "Ручки" для чтения/записи настройки из базы настроек. Скрывающие ключ и "пользовательское имя".
4. Умолчательные значения.
5. Поведение системы в зависимости от того или иного значения настройки. Считайте - "микро"-конечный-автомат.
6. Шаблон Publisher/subscriber на тот случай, если настройка изменится на лету.
7. "Визуализатор" данной ОТДЕЛЬНОЙ настройки в диалоге настройки системы.

Так вот я сделал такой шаблон кодогенерации которому достаточно "названия настройки" и "списка её значений" ПРЯМО в ТЗ. Как "дополнительных требований к прецеденту".

Далее берём элемент кода - на котором должна "жить" настройка, ставим ему связь РЕАЛИЗАЦИИ к данной настройке из требований и всё, и вся остальная обвязка - генерируется АВТОМАТИЧЕСКИ. С минимумом "рукописного" кода. Все ключи/значения, а также publisher/subscriber - генерируются. А также - "визуализатор" и "конечный автомат".

Интересно?

Написать как это выглядит на UML и Delphi? Или опять - "фуу - не бетон же месим..."?

Про UML И кодогенерацию

Тут мне один мой знакомый сказал - "UML с кодогенераицей для меня стало чем-то вроде перехода от Turbo Pascal к Delphi 1.0 с их RAD".

Надеюсь, что он не обидится за такую вольную цитату.

По сути - я с ним - 100% согласен.

Ещё раз позволю себе самоцитирование

http://18delphi.blogspot.ru/2013/03/objective-c-delphi.html

Что хочу добавить?

Динамическая типизация и "вызов методов по селектору" - это конечно круто и гибко.

Тем более, что про Python - мне тут уже "плешь проели".

Но я вот год с небольшим программирую на Objective-C параллельно с Delphi, но никак не могу отделаться от мысли, что в Objective-C в отличии от Delphi - я "что-то упускаю"... Что-то проходит мимо меня. Не "держу руку на пульсе".

Особенно это касается "чистого" Objective-C, а не связки Objective-C-C++-STL.

Что мне не хватает "статической типизации" компилятором или хотя бы полной UML-модели.

Приведу пример из реального (почти) проекта.

Есть некий функционал. Он возвращает список неких объектов. Самописных. В виде NSArray.

И функционал - вполне себе работал. И делал то, что надо.

Но потом нашлась ситуация в которой "набивать самописными объектами" NSArray стало "дорого". И стали туда набивать пары "одно число - другое".

А на "клиентском конце" стали смотреть conformsToProtocol. Если conforms - идём по одной ветке - значит там лежат "нужные" объекты, а если не conforms, то по другой. Значит там лежат "нужные пары". В виде NSArray из двух элементов.

Потом случилась ещё одна метаморфоза. И в "тот же самый" список стали помещать proxy-объекты.

А "клиентские концы" - опять модифицировали.

А "клиентских концов" - не один и не два.

И всё - "расползается как тараканы". Если бы это был бы типизированный язык, то компилятор бы это всё показал бы и можно было бы провести дельный (http://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%B2%D0%B5%D1%89%D0%B8) рефакторинг.

А так.. приходится - "мониторить концы"...

Считайте это "вымышленной историей"... Но поверьте - она - недалека от истины...

И не надо кстати рассказывать, что "надо обобщать клиентские концы". Я КОНЕЧНО это понимаю.

Посему - duck-typing и "вызов метода по селектору" - это КОНЕЧНО - КРУТО и ГИБКО, но ОЧЕНЬ быстро может привести к "аду и ужасу"... Хотя ДАЖЕ GoF - ОЧЕНЬ пропагандируют SmallTalk и его последователей.

И я ИХ ПОНИМАЮ.

Но не могу отделаться от мысли, что "теряю ощущение руки на пульсе".

Никого не хоте обидеть. Просто высказал свои мысли... Если вдруг есть вопросы - задавайте.

P.S. Но! Я не сказал, что Objective-C - "плохой" язык. Нет! Не подумайте этого. Иначе бы я не стал писать его сравнения с Delphi. Но! Objective-C - надо "уметь готовить". Гораздо "осторожнее", чем Delphi. Ну или "понимать грань" и "быстро" переходить от Objective-C к plain-C или C++-STL. Но с "гранью" - непросто.

P.P.S. Это не повод для "РЕЛИГИОЗНЫХ ВОЙН". Это повод - задуматься. И стараться учесть описанные выше ошибки.

Случайно решил почитать, что же написано в wiki про Delphi

пятница, 20 сентября 2013 г.

Поворчу. Цитата с "госуслуг"

"Используйте русскую раскладку клавиатуры при указании государственного номерного знака 
и номера водительского удостоверения."

Хочется спросить... Каким местом эти "программисты" думали?

четверг, 19 сентября 2013 г.

Если бы UML был бы ПРОМЫШЛЕННЫМ СТАНДАРТОМ

Что ПЕДАЛИРУЮТ авторы практически ВСЕХ книг про UML.

ТО ведущие разработчики ПО публиковали бы не только исходные коды и/или header'ы библиотек, но и UML-диаграммы классов этих самых библиотек.

А ЕЩЁ ЛУЧШЕ - диаграммы последовательности и диаграмм ПРЕЦЕДЕНТОВ (ведь библиотеки ТОЖЕ, имеют прецеденты использования, если верить авторам тех самых книг).

Насколько я знаю - ни IBM, ни Microsoft, ни Oracle, ни Apple, ни Embarcadero - этого не делают.

А это означает, что UML - отнюдь не промышленный стандарт, а нечто "эфимерное" и педалируемое авторами книг.

Авторы - молодцы. Я за последнее время на книги про UML потратил примерно 3000 рублей. И ничего из них не вынес. К сожалению.

До ПРОМЫШЛЕННОГО СТАНДАРТА - далеко. Похоже - ДЕЙСТВИТЕЛЬНО - "UML родился 5 мин назад". Ну по сравнению с чертежами.

Кстати о "чертежах и UML"

Вот тут:
http://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C)

-- по-моему - "чертёж" вполне к месту и вполне читабелен.

Хотя я бы метод NotifyObservers с интерфейса Observable - убрал бы. Оставил бы его ТОЛЬКО на конкретных реализациях. Ни к чему он на интерфейсе. Если кому-то "со стороны" надо его звать - я бы ввёл бы ВТОРОЙ интерфейс. ObservableNotify.

Коротенько. Люблю TAction

Есть одна ЗАСАДА.

Надо понимать, что OnUpdate - МАССОВО зовутся в Application.Idle.

Т.е. НАДО ПОНИМАТЬ, что они должны быть "максимально лёгкими". В плане использования процессора.

В "тяжёлых" случаях использование TAction.OnUpdate надо дополнять использованием шаблона "publisher/subscriber" (http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_patternhttp://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C).

Сколько хороших разработок похоронено

Lexicon
Иван Фёдоров
WordExpress
Atlantis

-- это всё - собственные текстовые процессоры под Windows. Писанный с НУЛЯ. Без всяких RichEdit'ов.

Всё похоронили OpenOffice и GoogleDocs.

Даже сайтов в интернете не осталось.

(Atlantis - был особенно хорош. Правда без таблиц.)

А жаль...

Один только TRichView остался -  http://trichview.com. Да и тот - просто ПРАВИЛЬНО позиционировался. Не как "текстовый процессор", а как "набор компонент".

Хочется пожелать удачи его автору.

Стороннее мнение про UML и чертежи

Один из моих корреспондентов любезно разрешил мне опубликовать следующее:

http://18delphi.blogspot.com/2013/09/uml.html

"Да, согласен. С комментарием не согласен - простите за выражение, отмазка. Почему-то никому не приходит в голову на пальцах объяснять даже простейшую деталь. Даже чертежи прокладки для крана (уж куда проще) существуют. Руками её очень даже можно сделать, только кран придется выбрасывать (горький опыт). Хотя... ремонтники работают без всяких чертежей и схем. Нам вон на даче полдома сделали без всяких чертежей. Вот только вот нюансов и тонкостей потом нашлось... По любому, что-то вроде схемы все равно рисуется. По-русски не объяснишь все.
Но все-таки с чертежами сравнивать, наверное, не совсем верно. Черчение очень давно сформировалось. А его использование повсеместно появилось все-таки примерно в 20-м веке. Объем работ привел к разделению труда, а значит понадобилось каждому объяснять на общем языке. Текст не годился - слишком долго и муторно.
UML по сравнению с чертежами появился даже не "вчера", а "пять минут назад". Черчение - это мировой стандарт с устоявшимися обозначениями. Есть тонкости оформления, но это тонкости, не более. Я прекрасно пойму чертеж, сделанный, скажем, в Америке. Оформить точно также не смогу, но пойму. Может будут какие-то тонкости, но будет множество литературы, которая мне это объяснит.
С UML все-таки не так все просто - его гибкость - для многих его недостаток. Он, как XML, база для какой-нибудь конкретной технологии, но не окончательное решение. Уж больно многое приходится допиливать, додумывать. Никто не обсуждает какой стрелочкой обозначать размер на чертеже - это жестко прописано. Будешь выпендриваться - заставят переделывать, т.к. такой чертеж никто не поймет. А в UML - пожалуйста.
Вообщем, как-нибудь разовью я эту тему. "

"Можешь написать "to be continue" в конце. У меня будет повод продолжить, а то никак не начну."

... to be continued

Ужас какие "закорючки"

http://pastebin.com/a1Kx0kcF

  1. data State st a = State (st -> (a, st))

Я тоже "баловался" подобным.

Писал что-то вроде.

INTEGER VAR X
>>> X

Или

ARRAY A
>>>[] A

Но склоняюсь в последнее время к следующему:

ЦЕЛОЕ X
"взять значение со стека и поместить в {X}"

МАССИВ A
"взять значение со стека и добавить в массив {A}"

-- по-моему - это конечно "многословнее". Но! Гораздо более понятнее.

Ну или хоты бы:

ЦЕЛОЕ X
10 =: X

МАССИВ A
20 "добавить в массив {A}"

Или:
ЦЕЛОЕ X
10 "присвоить в" X

МАССИВ A
20 "добавить в массив" A

Типа - самодокументируемый код - http://www.delphikingdom.ru/asp/talktopic.asp?ID=82&Order=0&Count=10&pNo=2

"Все было перепробовано. В итоге - "самодокументируемый код". Глядя на который безо всяких комментариев и схем понимаешь, о чем это. Сложно так проектировать и писать? Непросто. Может быть, это и есть высший пилотаж?:-)"


А "закорючки" - я бы оставил бы интегралам, тензорному счислению и уравнениям Максвелла.

P.S. И речь ОТНЮДЬ не идёт о "естественном языке" и тем более не об "искусственном интеллекте". И не о том, чтобы "это было просто писать". ОТНЮДЬ. Такие задачи - НИКТО не ставит. Формальная логика и грамматика - ВСЁ РАВНО там везде присутствует.

Но! Речь идёт о том, чтобы это хоты бы ЧИТАТЬ можно было бы "просто". Типа "по-русски".

Ссылка. О "студенческих"лицензиях для Delphi

http://namerec.blogspot.ru/2013/09/firemonkey.html

Что сказать... Правильную тему в общем-то автор поднимает.

Но уж очень в "резкой" форме ИМХО.

Но! Там правильно написано - "это Embarcadero НУЖДАЕТСЯ в студентах, а не они в ней".

"Ты помнишь как всё начиналось.." №2

Вот кстати тоже по-моему удачный пост - http://18delphi.blogspot.com/2013/04/blog-post_2.html

И вот - http://18delphi.blogspot.com/2013/04/blog-post.html

среда, 18 сентября 2013 г.

Мне вот кстати интересно

Интересен ли аналог TRichEdit под FireMonkey? Я - "знаю" как его "быстро" сделать.

P.S. http://18delphi.blogspot.ru/search/label/%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%20%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D0%BE%D0%B2

"Я бы лично посоветовал бы поглядеть в сторону CoreText и AttributedString от xCode.

Ну и в сторону - GoF."

"Не смотрите в сторону RTF как "модели документа в памяти". Смотрите на него не более чем на ВНЕШНИЙ формат. Внутри раскладывайте в СВОИ структуры. Лучше в древесные. И описываемые DTD. Смотрите в сторону XML. Это не значит, что "надо сделать кальку с XML". Конечно же - НЕТ. СМОТРИТЕ на него. Анализируйте и рождайте собственные БИНАРНЫЕ структуры "по образу и подобию"."

В общем - что надо сделать? Написать рендеринг ОТДЕЛЬНО взятого текстового параграфа. С текстом и шрифтовым оформлением. Всё остальное - из него выводится. А писать там в общем - несложно. Прямо как "кальку" с CoreText. Параграф с оформлением -> Frame -> DrawFrame.

Как писать? Взять GoF и "концепцию Glyph'ов".

Мой привет. Далеко не идеальный:

type
 TnevTextParaRenderInfo = class(TnevLeafRenderInfo)
 private
 // private fields
   f_L : InevLines;
   f_VS : InevTag;
   f_Children : TnevFormatInfoList;
 protected
 // overridden property methods
   function pm_GetViewSegmentsPlace: InevTag; override;
   function pm_GetLinesPlace: InevLines; override;
   procedure pm_SetLinesPlace(const aValue: InevLines); override;
   function pm_GetLocSpacing: TnevRect; override;
 protected
 // overridden protected methods
   procedure Cleanup; override;
     {* Функция очистки полей объекта. }
   function GetMaxLinesCount: Integer; override;
   procedure DoRecalc(const aView: InevViewMetrics); override;
 public
 // overridden public methods
   function GetInfoForChild(const aChild: InevObjectPrim): TnevFormatInfo; override;
   procedure DoInvalidateShape(const aShape: InevObject;
    aParts: TnevShapeParts); override;
   procedure WForce(aParts: TnevRenderingInfoParts); override;
 end;//TnevTextParaRenderInfo

...

procedure TnevTextParaRenderInfo.Cleanup;
//#UC START# *479731C50290_481F174D01BF_var*
//#UC END# *479731C50290_481F174D01BF_var*
begin
//#UC START# *479731C50290_481F174D01BF_impl*
 if (f_Children <> nil) then
  f_Children.ClearReferencesToParentFormatInfo;
 FreeAndNil(f_Children);
 f_L := nil;
 f_VS := nil;
 inherited;
//#UC END# *479731C50290_481F174D01BF_impl*
end;//TnevTextParaRenderInfo.Cleanup

function TnevTextParaRenderInfo.GetInfoForChild(const aChild: InevObjectPrim): TnevFormatInfo;
//#UC START# *4815C94A027A_481F174D01BF_var*
var
 l_Index : Integer;
 l_Item  : TnevFormatInfo;  
//#UC END# *4815C94A027A_481F174D01BF_var*
begin
//#UC START# *4815C94A027A_481F174D01BF_impl*
 Assert(aChild.InheritsFrom(k2_idLeafPara) OR
        aChild.InheritsFrom(k2_idTextSegment));
 if (f_Children = nil) then
  f_Children := TnevFormatInfoList.Create;
 with f_Children do
 begin
  for l_Index := 0 to Pred(Count) do
  begin
   l_Item := Items[l_Index];
   if l_Item.Obj.IsSame(aChild) then
   begin
    Result := l_Item;
    Exit;
   end;//l_Item.f_Obj.IsSame(aShape)
  end;//for l_Index
  l_Item := TnevFormatInfoFactory.CreateFormatInfo(aChild, Self, Metrics);
  try
   l_Item.LimitWidth := LimitWidth;
          // ^ - может быть здесь надо отступы учесть
   Add(l_Item);
   Result := l_Item;
  finally
   FreeAndNil(l_Item);
  end;//try..finally
 end;//with f_Children
//#UC END# *4815C94A027A_481F174D01BF_impl*
end;//TnevTextParaRenderInfo.GetInfoForChild

procedure TnevTextParaRenderInfo.DoInvalidateShape(const aShape: InevObject;
  aParts: TnevShapeParts);
//#UC START# *48172A690313_481F174D01BF_var*
//#UC END# *48172A690313_481F174D01BF_var*
begin
//#UC START# *48172A690313_481F174D01BF_impl*
 if (nev_spSegments in aParts) then
 begin
  if (f_VS <> nil) then
  begin
   f_VS := nil;
   Include(aParts, nev_spExtent);
  end;//f_VS <> nil
 end;//nev_spSegments in aParts
 inherited;
//#UC END# *48172A690313_481F174D01BF_impl*
end;//TnevTextParaRenderInfo.DoInvalidateShape

procedure TnevTextParaRenderInfo.WForce(aParts: TnevRenderingInfoParts);
//#UC START# *48175C1302A3_481F174D01BF_var*
//#UC END# *48175C1302A3_481F174D01BF_var*
begin
//#UC START# *48175C1302A3_481F174D01BF_impl*
 if (([nev_ripWidth, nev_ripLines, nev_ripHeight] * aParts) <> []) then
  FreeAndNil(f_Children);
 inherited;
//#UC END# *48175C1302A3_481F174D01BF_impl*
end;//TnevTextParaRenderInfo.WForce

function TnevTextParaRenderInfo.pm_GetViewSegmentsPlace: InevTag;
//#UC START# *4821DB2500CB_481F174D01BFget_var*
var
 l_Super       : InevTag absolute Result;
 l_SuperHandle : Integer;
 l_Exclude     : TevNormalSegLayerHandleSet;
 l_TextPara    : InevTextPara;

 function _AddLayer(const SegLst: InevTag; Index: Integer): Boolean; 

  function _AddSegment(const Seg: InevTag; Index: Integer): Boolean; 
  var
   S1 : InevTag;
  begin
   S1 := Seg.CloneTag;
   try
    evSegmentsLayer_AddSegment(l_TextPara, l_Super, S1);
   finally
    S1 := nil;
   end;{try..finally}
   Result := true;
  end;{_AddSegment}

 var
  l_Handle : Integer; 
 begin
  l_Handle := SegLst.IntA[k2_tiHandle];
  if (l_Handle > ev_slSuperposition) AND not (l_Handle in l_Exclude) then
   SegLst.IterateChildrenF(k2L2TIA(@_AddSegment));
  Result := true;
 end;{_AddLayer}

var
 l_Segments : InevTag;
 l_CC       : Integer;
//#UC END# *4821DB2500CB_481F174D01BFget_var*
begin
//#UC START# *4821DB2500CB_481F174D01BFget_impl*
 if (f_VS = nil) then
 begin
  if not Obj.QT(InevTextPara, l_TextPara) then
   Assert(false);
  l_Segments := l_TextPara.Attr[k2_tiSegments];
  if l_Segments.IsValid then
  begin
   if not l3IsNil(l_TextPara.Text) then
   begin
    l_CC := l_Segments.ChildrenCount;
    if (l_CC <= 0) then
    begin
     // - нет никакого оформления, значит и суперпозиция не нужна
     Result := k2NullTag;
     l_TextPara.Attr[k2_tiSegments] := nil;
     // - зачищаем странные сегменты
    end//l_CC <= 0
    else
    begin
     l_SuperHandle := 0;
     if Self.Parent <> nil then
      l_Exclude := Metrics.ExcludeSuper
     else
      l_Exclude := [];
     if (l_Exclude = []) AND (l_CC = 1) then
     begin
      // - не нужно копировать сегменты в суперпозицию
      Result := l_Segments.Child[0];
     end
     else
     begin
      Result := l_Segments.rAtomEx([k2_tiChildren, k2_tiHandle, l_SuperHandle]);
      if (Result = nil) OR not Result.IsValid then
      begin
       Result := l_Segments.cAtomEx([k2_tiChildren, k2_tiHandle, l_SuperHandle], nil);
       l_Segments.IterateChildrenF(k2L2TIA(@_AddLayer));
       l_Segments.DeleteChild(Result);
      end//not Result.IsValid
      else
       Assert(false);
     end;//l_Exclude = []..
    end;//l_CC <= 0
   end//not l3IsNil(Text)
   else
    Result := k2NullTag;
  end//l_Segments.IsValid
  else
   Result := k2NullTag;
  f_VS := Result;
 end//f_VS = nil
 else
  Result := f_VS;
//#UC END# *4821DB2500CB_481F174D01BFget_impl*
end;//TnevTextParaRenderInfo.pm_GetViewSegmentsPlace

function TnevTextParaRenderInfo.pm_GetLinesPlace: InevLines;
//#UC START# *4821DE24003D_481F174D01BFget_var*
//#UC END# *4821DE24003D_481F174D01BFget_var*
begin
//#UC START# *4821DE24003D_481F174D01BFget_impl*
 Result := f_L;
//#UC END# *4821DE24003D_481F174D01BFget_impl*
end;//TnevTextParaRenderInfo.pm_GetLinesPlace

procedure TnevTextParaRenderInfo.pm_SetLinesPlace(const aValue: InevLines);
//#UC START# *4821DE24003D_481F174D01BFset_var*
//#UC END# *4821DE24003D_481F174D01BFset_var*
begin
//#UC START# *4821DE24003D_481F174D01BFset_impl*
 f_L := aValue;
//#UC END# *4821DE24003D_481F174D01BFset_impl*
end;//TnevTextParaRenderInfo.pm_SetLinesPlace

function TnevTextParaRenderInfo.GetMaxLinesCount: Integer;
//#UC START# *4BC45843011E_481F174D01BF_var*
var
 l_Lines: InevLines;
//#UC END# *4BC45843011E_481F174D01BF_var*
begin
//#UC START# *4BC45843011E_481F174D01BF_impl*
 l_Lines := Lines;
 if l_Lines = nil then         
  Result := 1
 else
 begin
  Result := l_Lines.Count;
  Assert(Result > 0);
 end;//l_Lines = nil
//#UC END# *4BC45843011E_481F174D01BF_impl*
end;//TnevTextParaRenderInfo.GetMaxLinesCount

function TnevTextParaRenderInfo.pm_GetLocSpacing: TnevRect;
//#UC START# *4E5F3D1102B8_481F174D01BFget_var*
const
 cnTopSpace = nevInch div 8;
//#UC END# *4E5F3D1102B8_481F174D01BFget_var*
begin
//#UC START# *4E5F3D1102B8_481F174D01BFget_impl*
 Result := inherited pm_GetLocSpacing;
 if (Metrics.AACLike = nev_aacLeft) and EvIsParaHyperlink(Obj) then
 begin
  Result.Top := Result.Top + cnTopSpace;
  Result.Bottom := Result.Bottom + cnTopSpace;
 end;// if (Metrics.AACLike = nev_aacLeft) and EvIsParaHyperlink(Obj) then
//#UC END# *4E5F3D1102B8_481F174D01BFget_impl*
end;//TnevTextParaRenderInfo.pm_GetLocSpacing

procedure TnevTextParaRenderInfo.DoRecalc(const aView: InevViewMetrics);
//#UC START# *4E7094780214_481F174D01BF_var*
var
 l_Height       : Integer;
 l_InfoCanvas   : InevInfoCanvas;
 l_Map          : TevLineArray;
 l_InLimitWidth : Integer;

 procedure DoFormat{(const ETAOIN_SHRDLU_aSegmentAndFontTool: InevFontTool)};   
 var
  l_SoftEnterCount : Integer;
  l_Str            : Tl3PCharLen;    // - Строка параграфа.
  l_PrevWrap       : PAnsiChar;
  l_FontIndexes    : Tl3FontIndexes;
  l_AllowHyphen    : Boolean;
  l_ITabInfo       : Il3TabInfo;
  l_MTabInfo       : Il3TabInfo;
  l_MeasureCanvas  : InevInfoCanvas;
  l_CharSize       : Integer;
  l_Spaces         : TnevRect;

  procedure FormatStr(var theLimitWidth : Integer;
                      const aCurFont    : InevFont;
                      IsSeg             : Boolean;
                      var aStr          : Tl3PCharLen;
                      var theStrExtent  : TnevPoint);   
  var
   l_AddHyphen : Boolean;
   l_StrExtent : TnevPoint;

   procedure AddLine;
   begin{AddLine}
    l_PrevWrap := aStr.S;
    if (theStrExtent.Y = 0) then
     theStrExtent.Y := l_MeasureCanvas.AverageCharHeight;
    l_Map.Add((aStr.S - l_Str.S) div l_CharSize,
              theStrExtent, l_FontIndexes, l_AddHyphen);

    l_InfoCanvas.FinishTabs(l_ITabInfo);
    l_InfoCanvas.StartTabs(l_ITabInfo, Obj.TabStops);
    // - Переоткрываем новую строку

    if (l_InfoCanvas <> l_MeasureCanvas) then 
    begin
     l_MeasureCanvas.FinishTabs(l_MTabInfo);
     l_MeasureCanvas.StartTabs(l_MTabInfo, Obj.TabStops);
     // - Переоткрываем новую строку
    end;//l_InfoCanvas <> l_MeasureCanvas

    Inc(l_Height, theStrExtent.Y);
    theStrExtent := nevPt0;
    l_StrExtent := nevPt0;
    l_FontIndexes := [l_InfoCanvas.Font.Index];
    theLimitWidth := l_InLimitWidth - l_Spaces.Left;
   end;//AddLine

  var
   l_WrappedStr : Tl3PCharLen;
   l_OTabInfo   : Il3TabInfo;

   procedure MeasureStr(aNeedTrim: Boolean);
   var
    l_Str     : Tl3WString;
    l_TabInfo : Il3TabInfo;
    l_Len     : Integer;
   begin//MeasureStr
    if aNeedTrim then
     l_Str := l3RTrim(l_WrappedStr)
    else
     l_Str := l_WrappedStr;
    if (l_InfoCanvas = l_MeasureCanvas) then
    begin
     l_MeasureCanvas.StartTabs(l_TabInfo, l_OTabInfo);
     with l_MeasureCanvas.TextExtent(l_Str).P do
     begin
      Inc(theStrExtent.P.X, X);
      Inc(l_StrExtent.P.X, X);
      if l_AddHyphen then
      begin
       Inc(theStrExtent.P.X, evHyphenWidth);
       Inc(l_StrExtent.P.X, evHyphenWidth);
      end;//l_AddHyphen
      if (Y > theStrExtent.Y) then
      begin
       theStrExtent.Y := Y;
       l_StrExtent.Y := Y;
      end;//Y > theStrExtent.Y
     end;//with l_MeasureCanvas.TextExtent(l_Str).P
     l_MeasureCanvas.FinishTabs(l_TabInfo);
    end//l_InfoCanvas = l_MeasureCanvas
    else
    begin
     l_MeasureCanvas.Font := l_InfoCanvas.Font;
     l_MeasureCanvas.StartTabs(l_TabInfo, l_OTabInfo);
     with l_MeasureCanvas.TextExtent(l_Str).P do
     begin
      Inc(theStrExtent.P.X, X);
      if l_AddHyphen then 
       Inc(theStrExtent.P.X, evHyphenWidth);
      if (Y > theStrExtent.Y) then
       theStrExtent.Y := Y;
     end;//with l_MeasureCanvas.TextExtent(l_Str).P
     l_MeasureCanvas.FinishTabs(l_TabInfo);
     l_InfoCanvas.StartTabs(l_TabInfo, l_OTabInfo);
     with l_InfoCanvas.TextExtent(l_Str).P do
     begin
      Inc(l_StrExtent.P.X, X);
      if l_AddHyphen then
       Inc(l_StrExtent.P.X, evHyphenWidth);
      if (Y > l_StrExtent.Y) then
       l_StrExtent.Y := Y;
     end;//with l_MeasureCanvas.TextExtent(l_Str).P
     l_InfoCanvas.FinishTabs(l_TabInfo);
    end;//l_InfoCanvas = l_MeasureCanvas
   end;//MeasureStr

  var
   l_WrapPos      : Integer;  { - Позиция соответствующая максимальной ширине. }
   l_SoftEnterStr : PAnsiChar; { - Позиция SoftEnter'а. }
   l_NoTabs       : Boolean;
   l_InTable      : Boolean;
  begin//FormatStr
   l_StrExtent := theStrExtent;
   with l_InfoCanvas do
   begin
    Font := aCurFont;
    Include(l_FontIndexes, Font.Index);
   end;//with l_InfoCanvas
   l_NoTabs := False;
   while True do
   begin
    l_AddHyphen := False;
    l_OTabInfo := l_InfoCanvas.TabInfo;
    l_WrapPos := l_InfoCanvas.Pos2IndexQ(theLimitWidth - l_StrExtent.X, aStr, l_NoTabs);
    Assert(l_WrapPos >= 0);
    //l_WrapPos := l_InfoCanvas.Pos2Index(theLimitWidth - l_StrExtent.X, aStr);
    if (l_WrapPos <= 0) then
    begin
     if (l_StrExtent.X <= 0) then
      l_WrapPos := 1
     else
     if l3Same(aStr, l_Str) then
     //if (aStr.S = l_Str.S) then
     // - если често - я не до конца понял, что это за проверка и какая из двух
     //   проверок правильнее.
      Break
{     else
     if (aStr.SLen > 0) then
      Assert(false)};
    end;//l_WrapPos <= 0
    l_WrappedStr := aStr;
    if (l_WrapPos >= aStr.SLen) then
     l_WrappedStr.SLen := l_WrapPos
    else
     l_WrappedStr.SLen := l3Utils.l3FindNextLine(aStr, l_WrapPos);
    // - Нашли где должна кончаться строка без учета SoftEnter.
    if (l_SoftEnterCount > 0) then
    begin
     // - Еще есть SoftEnter'ы - надо их обработать.
     l_SoftEnterStr := ev_lpScan(cc_SoftEnter, l_WrappedStr.S, l_WrappedStr.SLen);
     if (l_SoftEnterStr <> nil) then
     begin
      Dec(l_SoftEnterCount);
      l_WrappedStr.SLen := l_SoftEnterStr - l_WrappedStr.S;
      aStr.Init(l_SoftEnterStr + 1, aStr.SLen - (l_WrappedStr.SLen + 1), aStr.SCodePage);
      MeasureStr(False); // - Измеряем размеры полученной строки.
      AddLine;  // - Добавляем строку с SoftEnter'ом.
      if (aStr.SLen = 0) then
       Break // - Строка закончилась
      else
       Continue; // - Продолжаем форматировать остаток строки.
     end;//l_SoftEnterStr <> nil
    end;//l_SoftEnterCount > 0
    l_InTable := Obj.Owner.IsValid and Obj.Owner.InheritsFrom(k2_idTableCell); 
    if (l_WrapPos < aStr.SLen) then
    begin
     // - Обрабатываем переносы по слогам
     if l_AllowHyphen then
     begin
      case l3SplitBySlog(aStr, l_WrapPos, l_InTable) of
       l3_ssHyphen : l_WrappedStr.SLen := l_WrapPos;
       l3_ssYes    :
       begin
        l_WrappedStr.SLen := l_WrapPos;
        l_AddHyphen := True;
       end;//l3_ssYes
      end;//case l3SplitBySlog(aStr, l_WrapPos)
     end//l_AllowHyphen
     else
     (*with l_WrappedStr do
      if not evWhiteSpace(S[Pred(SLen)]) then*)
      if (l_StrExtent.X <= 0) then
        l_AddHyphen := l3SplitBySlog(aStr, l_WrappedStr.SLen, l_InTable) = l3_ssYes;
    end;//l_AllowHyphen
    aStr.Shift(l_WrappedStr.SLen);
    if (aStr.SLen = 0) then
    begin
     // - Строка вся кончилась. 
     MeasureStr(False); // - Измеряем размеры полученной строки. 
     Break; // - Выходим, т.к. добавлять нецелую строку не надо. 
    end;//aStr.SLen
    if not l_AddHyphen AND
       // не было переноса
       (l_WrappedStr.S > l_PrevWrap) AND
       (l_WrappedStr.SLen > 0) then
       // - предыдущие две проверки защищают от зацикливания и выхода за границы
    begin
     if not l3IsWhiteSpace((aStr.S-1)^) then
      // - разрезали посередине слова
      if l3IsWhiteSpace((l_WrappedStr.S-1)^)
          // - пробел был где-то раньше
          OR IsSeg
          // - это сегмент, который не надо резать пополам
         then
     begin
      // - Учитываем ситуацию когда сегмент режется посередине слова
      aStr.Init(l_WrappedStr.S, aStr.SLen + l_WrappedStr.SLen, aStr.SCodePage);
      l_WrappedStr.SLen := 0;
     end;//evWhiteSpace(l_WrappedStr.S-1)^)
    end;//not l_AddHyphen..
    MeasureStr(True); // - Измеряем размеры полученной строки. 
    AddLine; // - Добавляем полученную строку. 
   end;//while True
  end;//FormatStr

 var
  l_CurStr     : Tl3PCharLen;
  l_StrExtent  : TnevPoint;
  l_LimitWidth : Integer;
  l_ParaFont   : InevFont;

  function FormatSegment(const aSegment: InevTag; Index: Integer): Boolean;
  var
   l_SegFont   : InevFont;
   l_SegStart  : Integer;
   l_SegFinish : Integer;
  begin//FormatSegment
   Result := True;
   with aSegment do
   begin
    l_SegStart := Pred(IntA[k2_tiStart]);
    l_SegFinish := IntA[k2_tiFinish];
   end;//with aSegment
   { Обрабатываем часть строки до выделения: }
   l_CurStr.SLen := Min(l_SegStart, l_Str.SLen) - (l_CurStr.S - l_Str.S);
   if (l_CurStr.SLen > 0) then
    FormatStr(l_LimitWidth, l_ParaFont, false, l_CurStr, l_StrExtent);
   { Обрабатываем отрезок выделения. }
   l_CurStr.SLen := Min(l_SegFinish - l_SegStart,
                        l_Str.SLen - (l_CurStr.S - l_Str.S));
   if (l_CurStr.SLen > 0) then
   begin
    //evCheckRenderedObject(aView, aSegment, Obj, aSegmentAndFontTool, Self);
    l_SegFont := Self.InfoForChild(TnevSegmentObject.Make(aSegment)).Font[true];
    //l_SegFont := aSegmentAndFontTool.ETAOIN_SHRDLU_GetObjIFont(aView, aSegment, true, Self);
    try
     FormatStr(l_LimitWidth, l_SegFont, true, l_CurStr, l_StrExtent);
    finally
     l_SegFont := nil;
    end;//try..finally
   end;//l_CurStr.SLen > 0
  end;//FormatSegment

  function CheckSegmentHeight(const aSegment: InevTag; Index: Integer): Boolean;
  var
   l_SegFont   : InevFont;
   l_SegStart  : Integer;
   l_SegFinish : Integer;
  begin//
   Result := True;
   with aSegment do
   begin
    l_SegStart := Pred(IntA[k2_tiStart]);
    l_SegFinish := IntA[k2_tiFinish];
   end;//with aSegment
   l_CurStr.S := l_Str.S + l_SegStart;
   l_CurStr.SLen := l_SegFinish - l_SegStart;
   if (l_CurStr.SLen > 0) then
   begin
    //evCheckRenderedObject(aView, aSegment, Obj, aSegmentAndFontTool, Self);
    l_SegFont := Self.InfoForChild(TnevSegmentObject.Make(aSegment)).Font[true];
    //l_SegFont := aSegmentAndFontTool.ETAOIN_SHRDLU_GetObjIFont(aView, aSegment, true, Self);
    try
     l_MeasureCanvas.Font := l_SegFont;
     l_Height := Max(l_Height, l_MeasureCanvas.TextExtent(l_Str).Y);
    finally
     l_SegFont := nil;
    end;//try..finally
   end;//l_CurStr.SLen > 0
  end;//CheckSegmentHeight

 var
  l_FirstIndent : Integer;
  l_SLI         : Tl3SingleLineInfo;
 begin//DoFormat
  (*if (RecalcThread <> nil) AND not RecalcThread.ChangeCanvas then
   l_MeasureCanvas := l_InfoCanvas
  else *)begin
   l_MeasureCanvas := aView.InfoCanvas;
   //l_MeasureCanvas := l_InfoCanvas;
   // http://mdp.garant.ru/pages/viewpage.action?pageId=88639598
   // - может статься, что что-то не доделал
   // Точно не доделал:
   // http://mdp.garant.ru/pages/viewpage.action?pageId=195758154
   //l_MeasureCanvas := ETAOIN_SHRDLU_l3CrtIC;
   if l_InfoCanvas.EQ(l_MeasureCanvas) then
    l_MeasureCanvas := l_InfoCanvas;
  end;//RecalcThread <> nil
  l_Spaces := Self.Get_Spacing;
  l_Map := TevLineArray.Create;
  try
   Self.wMap(l_Map);
  finally
   l_Map := l_Map.Free;
  end;//try..finally
  try
   if not Obj.TreatCollapsedAsHidden OR not aView.IsTagCollapsed(Obj) then
   begin
    l_AllowHyphen := Obj.BoolA[k2_tiAllowHyphen];
    l_Str := Obj.Text;
    if (l_Str.SCodePage = CP_Unicode) then
     l_CharSize := SizeOf(WideChar)
    else
     l_CharSize := SizeOf(ANSIChar);
    l_ParaFont := Self.Get_Font(true);
    //l_ParaFont := aSegmentAndFontTool.ETAOIN_SHRDLU_GetObjIFont(aView, Obj, False, Self);
    try
     l_InfoCanvas.Font := l_ParaFont;
     if (l_InfoCanvas <> l_MeasureCanvas) then
      l_MeasureCanvas.Font := l_ParaFont;
     l_InfoCanvas.StartTabs(l_ITabInfo, Obj.TabStops);
     if (l_InfoCanvas <> l_MeasureCanvas) then
      l_MeasureCanvas.StartTabs(l_MTabInfo, Obj.TabStops);
     try
      Dec(l_InLimitWidth, l_Spaces.Right);
      if l3IsNil(l_Str) then
      begin
       l_Height := l_MeasureCanvas.AverageCharHeight;
       l_Map.Add(0, Tl3_Point_C(0, l_Height));
       if (Obj.IntA[k2_tiJustification] = Ord(ev_itPreformatted)) then
        Self.wWidth(0)
       else
        Self.wWidth(l_InLimitWidth);
      end//l3IsNil(l_Str)
      else
      if (Obj.IntA[k2_tiJustification] = Ord(ev_itPreformatted)) and Obj.IsValid then
      begin
       l_StrExtent := l_MeasureCanvas.TextExtent(l_Str);
       l_Height := l_StrExtent.Y;
       with Self.ViewSegments do
        if IsValid then
        begin
         IterateChildrenF(k2L2TIA(@CheckSegmentHeight));
         l_StrExtent.Y := l_Height;
        end;//IsValid
       // Не уверен, что такая проверка нужна... 
       l_Map.Add(l_Str.SLen, l_StrExtent);
       Self.wWidth(l_StrExtent.X + l_Spaces.Left);
      end//IntA[k2_tiJustification] = Ord(ev_itPreformatted)
      else
      begin
       Self.wWidth(l_InLimitWidth);
       l_FirstIndent := Self.Get_FirstIndent;
       if (l_Spaces.Left >= l_InLimitWidth - 100) OR
          (l_FirstIndent >= l_InLimitWidth - 100) then
       begin
        l_Height := l_MeasureCanvas.AverageCharHeight;
        l_Map.Add(0, Tl3_Point_C(0, l_Height));
       end//l_Spaces.Left >= l_InLimitWidth - 100
       else
       begin
        l_Height := 0;
        l_StrExtent := nevPt0;
        l_LimitWidth := l_InLimitWidth - l_FirstIndent;
        l_FontIndexes := [];
        l_SoftEnterCount := l3CountOfChar(cc_SoftEnter, l_Str);
        l_CurStr := l_Str;
        l_PrevWrap := l_Str.S;
        with Self.ViewSegments do
         if IsValid then
         begin
          IterateChildrenF(k2L2TIA(@FormatSegment));
          l_CurStr.SLen := l_Str.SLen - (l_CurStr.S - l_Str.S);
          FormatStr(l_LimitWidth, l_ParaFont, false, l_CurStr, l_StrExtent);
          // - доформатируем хвост после последнего сегмента
         end//IsValid
         else
          FormatStr(l_LimitWidth, l_ParaFont, false, l_CurStr, l_StrExtent);
        if (l_StrExtent.Y <= 0) then
        begin
         l_MeasureCanvas.Font := l_ParaFont;
         l_StrExtent.Y := l_MeasureCanvas.AverageCharHeight;
        end;//l_StrExtent.Y <= 0
        l_Map.Add(l_Str.SLen, l_StrExtent, l_FontIndexes, false);
        Inc(l_Height, l_StrExtent.Y);
       end;//l_Spaces.Left >=..
      end;//..ev_itPreformatted..
     finally
      if (l_InfoCanvas <> l_MeasureCanvas) then
       l_MeasureCanvas.FinishTabs(l_MTabInfo);
      l_InfoCanvas.FinishTabs(l_ITabInfo);
     end;//try..finally
    finally
     l_ParaFont := nil;
    end;//try..finally
 (*   with Obj.rAtomEx([k2_tiFrame, k2_tiFrameUp]) do
     if IsValid then
      Inc(l_Height, IntA[k2_tiWidth]);*)
   end//not Obj.TreatCollapsedAsHidden OR not aView.IsTagCollapsed(Obj)
   else
   begin
    Self.wWidth(0);
    l_Height := 0;
   end//not Obj.TreatCollapsedAsHidden OR not aView.IsTagCollapsed(Obj)
   ;
   Inc(l_Height, l_Spaces.Top);
   Inc(l_Height, l_Spaces.Bottom);
  finally
   if (l_Map <> nil) then
   begin
    if (l_Map.Count < 1) then
    begin
     // - удаляем ненужное в данном случае форматирование
     l_Map := nil;
     Self.wMap(nil);
    end//l_Map.Count < 1
    else
    if (l_Map.Count = 1) then
    begin
     if (Obj.IntA[k2_tiJustification] = Ord(ev_itPreformatted)) then
     begin
      // - удаляем ненужное в данном случае форматирование
      l_Map := nil;
      Self.wMap(nil);
     end//IntA[k2_tiJustification] = Ord(ev_itPreformatted)
     else
     begin
      with PevLineInfo(l_Map.Items[0])^ do
       l_SLI := Tl3SingleLineInfo.Make(LE.X, LE.Y, FI);
      try
       l_Map := nil;
       Self.wMap(l_SLI);
      finally
       FreeAndNil(l_SLI);
      end;//try..finally
     end;//IntA[k2_tiJustification] = Ord(ev_itPreformatted)
    end;//l_Map.Count <= 1
   end;//l_Map <> nil
  end;//try..finally
 end;//DoFormat

{var
 l_SegmentAndFontTool : InevFontTool;}
//#UC END# *4E7094780214_481F174D01BF_var*
begin
//#UC START# *4E7094780214_481F174D01BF_impl*
 l_InLimitWidth := Self.rLimitWidth;
 l_Height := 0;
 Obj.DoLoad;
 l_InfoCanvas := aView.FormatCanvas;
// l_InfoCanvas := aView.InfoCanvas;
 try
  if Self.IsHidden(false,
                  (l_InfoCanvas <> nil) AND
                  l_InfoCanvas.Printing) then
  begin
   Self.wWidth(0);
   Self.wMap(nil);
  end//Obj.IsHiddenPrim(Self, aView.HiddenStyles)
  else
  begin
(*   l_SegmentAndFontTool := aView.FontTool{ As InevFontTool};
   try*)
(*    l_SegmentAndFontTool.ForPrinting := (l_InfoCanvas.Printing
                                         {$IFNDef Nemesis}OR not aView.IsWebStyle{$EndIf});*)
    l_InfoCanvas.Lock;
    try
     l_InfoCanvas.PushBC;
     l_InfoCanvas.PushFC;
     // - Запоминаем - чтобы не испортить фон для отрисовки
     try
      DoFormat{(l_SegmentAndFontTool)};
     finally
      l_InfoCanvas.PopFC;
      l_InfoCanvas.PopBC;
     end;//try..finally
    finally
     l_InfoCanvas.Unlock;
    end;//try..finally
(*   finally
    l_SegmentAndFontTool := nil;
   end;//try..finally*)
  end;//not Obj.IsHiddenPrim(Self, aView.HiddenStyles)
 finally
  l_InfoCanvas := nil;
 end;//try..finally
 Self.wHeight(l_Height);
//#UC END# *4E7094780214_481F174D01BF_impl*
end;//TnevTextParaRenderInfo.DoRecalc

P.P.S. Говоря о "портянках кода" - наверное теперь понятно, что я имею в виду. Но я (пока) не знаю - как это улучшить.

P.P.P.S. Этот код в таком (или примерно таком) виде "тянется" аж с 1996 (!) года. Я бы хотел его улучшить. Но пока - не понял как. При всей моей любви к "паттернам". Интересно было бы заглянуть ВНУТРЬ CoreText. И его Frame и DrawFrame.

P.P.P.P.S. Многое менялось за эти годы. COM|CORBA пришли и "почти ушли". Интерфейсы. Примеси. Шаблоны. UML. Но этот код как был "странным". Так и остался. Зато - "вполне эффективным". Это ещё вы не видели кода "рисования фрейма". Там - "ещё веселее".

Кстати. Вот и он:

procedure TevTextParaPainterEx.CheckDrawFocused;
//#UC START# *502268560274_49DA30D901B6_var*
//#UC END# *502268560274_49DA30D901B6_var*
begin
//#UC START# *502268560274_49DA30D901B6_impl*
 if (Area.rView.As_InevView.Metrics.AACLike = nev_aacLeft) then
  Area.rCanvas.NotFocused := False;
//#UC END# *502268560274_49DA30D901B6_impl*
end;//TevTextParaPainterEx.CheckDrawFocused

procedure TevTextParaPainterEx.CheckInvertInLeftAAC;
//#UC START# *5052EF710307_49DA30D901B6_var*
//#UC END# *5052EF710307_49DA30D901B6_var*
begin
//#UC START# *5052EF710307_49DA30D901B6_impl*
 if (Area.rView.As_InevView.Metrics.AACLike > nev_aacNone) and Area.rCanvas.Invert then
  Area.rCanvas.Font.ForeColor := clBlack
//#UC END# *5052EF710307_49DA30D901B6_impl*
end;//TevTextParaPainterEx.CheckInvertInLeftAAC

procedure TevTextParaPainterEx.DrawArrow(aRectHeight: Integer);
//#UC START# *50A4D6E90261_49DA30D901B6_var*
var
 l_Delta     : Integer;
 l_ParaH     : Integer;
 l_Width     : Integer;
 l_Height    : Integer;
 l_ImageInfo : PnevControlImageInfo;
//#UC END# *50A4D6E90261_49DA30D901B6_var*
begin
//#UC START# *50A4D6E90261_49DA30D901B6_impl*
 if (Area.rView.As_InevView.Metrics.AACLike = nev_aacLeft) and Area.rCanvas.Invert and ParentPainter.IsSelectedOneWholePara(ParaX) then
  with Area.rCanvas do
  begin
   l_ImageInfo := FormatInfo.ImageInfo;
   if l_ImageInfo <> nil then
   begin
    l_Height := DP2LP(PointX(l_ImageInfo.rImageList.Height)).X;
    l_ParaH := (aRectHeight + Spaces.Top + Spaces.Bottom);
    l_Delta := (l_ParaH - l_Height) div 2;
    l_Delta := l_Delta + l_Height - l_ParaH + Spaces.Bottom;
    l_Width := DP2LP(PointX(l_ImageInfo.rImageList.Width)).X;
    MoveWindowOrg(l3Point(l_Width + (l3Inch div 16), l_Delta));
    FillRect(l3SRect(0, 0, l_ImageInfo.rImageList.Width, l_ImageInfo.rImageList.Height));
    l_ImageInfo.rImageList.Draw(As_Il3Canvas, 0, 0, l_ImageInfo.rFirstIndex);
   end; // if l_ImageInfo <> nil then
  end; // if Area.rView.As_InevView.Metrics.AACLike then
//#UC END# *50A4D6E90261_49DA30D901B6_impl*
end;//TevTextParaPainterEx.DrawArrow

procedure TevTextParaPainterEx.ValidateSegFont(const aSeg: InevTag);
//#UC START# *4D664903006E_49DA30D901B6_var*
//#UC END# *4D664903006E_49DA30D901B6_var*
begin
//#UC START# *4D664903006E_49DA30D901B6_impl*
 if (Area.rView.As_InevView.Metrics.AACLike > nev_aacNone) and Area.rCanvas.Invert then
  if aSeg.InheritsFrom(k2_idHyperlink) then
    Area.rCanvas.Font.ForeColor := clBlack;
//#UC END# *4D664903006E_49DA30D901B6_impl*
end;//TevTextParaPainterEx.ValidateSegFont

procedure TevTextParaPainterEx.ValidateParaFont;
//#UC START# *4D725A550218_49DA30D901B6_var*
//#UC END# *4D725A550218_49DA30D901B6_var*
begin
//#UC START# *4D725A550218_49DA30D901B6_impl*
 // ничего не делаем
//#UC END# *4D725A550218_49DA30D901B6_impl*
end;//TevTextParaPainterEx.ValidateParaFont

procedure TevTextParaPainterEx.Release;
//#UC START# *479F2AFB0397_49DA30D901B6_var*
//#UC END# *479F2AFB0397_49DA30D901B6_var*
begin
//#UC START# *479F2AFB0397_49DA30D901B6_impl*
 FreeAndNil(f_LinePainter);
 inherited;
//#UC END# *479F2AFB0397_49DA30D901B6_impl*
end;//TevTextParaPainterEx.Release

procedure TevTextParaPainterEx.GetFramePartPrim(const aFrame: InevTag;
  anIndex: Tl3FramePartIndex;
  var thePart: TnevFramePart);
//#UC START# *4804B9BB0383_49DA30D901B6_var*
var
 l_Style : InevTag;
 l_N     : InevPara;
//#UC END# *4804B9BB0383_49DA30D901B6_var*
begin
//#UC START# *4804B9BB0383_49DA30D901B6_impl*
 inherited;
 if (anIndex = l3_fpiUp) then
  if (Spaces.Top > 0) AND not ParaX.Attr[k2_tiStyle].BoolA[k2_tiHeaderHasOwnSpace] then
  begin
   thePart.rDrawType := lpdDraw;
   thePart.rColor := clBlack;
   thePart.rWidth := 0;
   thePart.rSpaceBefore := (l3Inch div 8) div 2;
   thePart.rSpaceAfter := (l3Inch div 8) div 2;
  end;//Spaces.Top > 0..
 if (thePart.rDrawType = lpdDraw) AND (anIndex = l3_fpiLeft) then
  thePart.rSpaceBefore := Max(Min(f_FirstIndent, Spaces.Left) - l3Inch div 16, 0);
 if (thePart.rDrawType <> lpdHidden) then
 // - тут прячем лишние рамки (если идёт пачка параграфов с одним стилем)
 begin
  l_Style := ParaX.Attr[k2_tiStyle];
  if l_Style.IsValid then
  begin
   Case anIndex of
    l3_fpiUp:
    begin
     l_N := evPrevOverallPara(ParaX);
     if (l_N <> nil) AND l_N.IsValid AND
      (l_Style.IntA[k2_tiHandle] = l_N.IntA[k2_tiStyle]) AND
      l_N.Attr[k2_tiFrame].IsValid then
     thePart.rDrawType := lpdHidden;
    end;//l3_fpiUp
    l3_fpiDown:
    begin
     l_N := evNextOverallPara(ParaX);
     if (l_N <> nil) AND l_N.IsValid AND
      (l_Style.IntA[k2_tiHandle] = l_N.IntA[k2_tiStyle]) AND
      l_N.Attr[k2_tiFrame].IsValid then
     thePart.rDrawType := lpdHidden;
    end;//l3_fpiDown
   end;//Case anIndex
  end;//l_Style.IsValid
 end;//thePart.rDrawType <> lpdHidden
 if (thePart.rDrawType = lpdDraw) then
 begin
  if (anIndex = l3_fpiDown) then
  begin
   thePart.rSpaceBefore := Max(thePart.rSpaceBefore, l3Inch div 16);
   thePart.rSpaceAfter := Max(thePart.rSpaceAfter, l3Inch div 32);
  end;//anIndex = l3_fpiDown
 end;//thePart.rDrawType = lpdDraw
//#UC END# *4804B9BB0383_49DA30D901B6_impl*
end;//TevTextParaPainterEx.GetFramePartPrim

procedure TevTextParaPainterEx.InitBottom(var theBottom: InevBasePoint;
  var theCellBottom: InevBasePoint);
//#UC START# *4804BC800172_49DA30D901B6_var*
var
 l_Pt : TnevPoint;
//#UC END# *4804BC800172_49DA30D901B6_var*
begin
//#UC START# *4804BC800172_49DA30D901B6_impl*
 theCellBottom := nil;
 if (Map <> nil) then
 // - эта проверка НУЖНА, например для вложенных объектов (формул)
  with Map.Bounds do
  begin
   l_Pt := l3Point(Left, Bottom);
   if not DrawCompleted then
   // ^ - http://mdp.garant.ru/pages/viewpage.action?pageId=132220046
    Dec(l_Pt.P.Y, l3Epsilon);
   theBottom.AsLeaf.InitPointByPt(Area.rView.As_InevView, l_Pt, Map);
   if (Bottom > Top + Spaces.Top) then
   // - !!! вместо этой проверки для текстовых параграфов надо использовать
   //       номер строки, т.к. он всё равно известен.
    theBottom := TnevAfterEndPoint.Make(Area.rView.As_InevView, theBottom);
  end;//with Map.Bounds
//#UC END# *4804BC800172_49DA30D901B6_impl*
end;//TevTextParaPainterEx.InitBottom

procedure TevTextParaPainterEx.DoDrawFrameText(aTop: Boolean);
//#UC START# *4804C35B00B2_49DA30D901B6_var*
var
 l_Space : Integer;
 l_Style : InevTag;
 l_Text  : Tl3PCharLen;
 l_Decor : InevTag;
 l_DT    : TnevDecorType;
//#UC END# *4804C35B00B2_49DA30D901B6_var*
begin
//#UC START# *4804C35B00B2_49DA30D901B6_impl*
 f_TopLine := Succ(TopAnchor.VertPosition(Area.rView.As_InevView, FormatInfo));
 if aTop then
  l_Space := Spaces.Top
 else
  l_Space := Spaces.Bottom;
 if (l_Space > 0) then
 begin
  if (aTop AND
      (f_TopLine = 1)
     ) OR
     
     (not aTop {AND
      (f_TopLine = FormatInfo.Lines.Count) AND}
     ) then
  begin
   if aTop then
    l_DT := nev_dtHeader
   else
    l_DT := nev_dtFooter;
   l_Decor := FormatInfo.DecorObj(l_DT);
   l_Style := ParaX.Attr[k2_tiStyle];
   if l_Style.IsValid then
   begin
    l_Text := l_Style.PCharLenA[k2_tiShortName];
    if not l3IsNil(l_Text) then
     InternalDrawFrameText(l_Text, aTop, l_Space, l_DT, l_Decor, f_FirstIndent)
    else
    if l_Decor.IsValid then
     InternalDrawFrameText(Tl3PCharLen(cc_EmptyStr), aTop, l_Space, l_DT, l_Decor, f_FirstIndent)
   end;//l_Style.IsValid
  end;//aTop
 end;//l_Space > 0
//#UC END# *4804C35B00B2_49DA30D901B6_impl*
end;//TevTextParaPainterEx.DoDrawFrameText

function TevTextParaPainterEx.DrawLeaf: Boolean;
//#UC START# *4804C81000B9_49DA30D901B6_var*
var
 LH              : Integer; {- высота i-й строки}
 l_Justification : TevIndentType; {- тип выравнивания параграфа}
 l_ParaFont      : Il3Font;
 l_ParaS         : Tl3PCharLen;
 l_IsHyperlink   : Boolean;

 procedure DrawIndent(First     : Boolean;
                      DX        : Integer;
                      LL        : Boolean;
                      AddHyphen : Boolean);
 var
  D              : Integer;
  HardEnterWidth : Integer;
  l_PMStr        : Tl3PCharLenPrim;
 begin//DrawIndent
  with Area.rCanvas do
  begin
   if not First AND AddHyphen then
    ExtTextOut(l3Point0,
                      l3Rect(0, 0, evHyphenWidth, LH),
                      cc_HyphenStr);
   if ((DX > 0) AND (l_Justification <> ev_itWidth)) then
   begin
    if ((First AND (l_Justification in [ev_itRight, ev_itCenter])) OR
       (not First AND
        ((l_Justification in [ev_itLeft, ev_itCenter]) OR DrawSpecial))) then
    begin
     D := 0;
     case l_Justification of
      ev_itLeft:
       if not First then
        D := DX;
      ev_itRight:
       Inc(D, DX);
      ev_itCenter:
       if First then
        Inc(D, DX div 2)
       else
        Inc(D, DX - (DX div 2));
      ev_itWidth: {D уже равен X0};
     end;//case l_Justification
     if not First AND DrawSpecial AND LL then
     begin
      if (ParaMarkStyle = ev_pmCell) then
       l_PMStr := cc_CurrencyStr
      else
       l_PMStr := cc_HardEnterStr;
      if l3IsNil(l_ParaS) then
      begin
       Font := l_ParaFont;                                                 
       ValidateParaFont;
      end;//l3IsNil(l_ParaS)
      PushFC;
      try
       with Font do
       begin
        Name := def_ArialFontName;
        Style := [];
       end;//with Font
      finally
       PopFC;
      end;//try..finally
      HardEnterWidth := TextExtent(l_PMStr).X;
      if (HardEnterWidth > D) then
       D := HardEnterWidth;
      if (SelRange <> nil) AND SelRange.ContainsEnd(Area.rView.As_InevView) then
      begin
       BeginInvert;
       try
        ExtTextOut(l3Point0, l3Rect(0, 0, HardEnterWidth, LH), l_PMStr);
       finally
        EndInvert;
       end;//try..finally
      end//(SelRange <> nil) AND SelRange.ContainsEnd
      else
       ExtTextOut(l3Point0, l3Rect(0, 0, HardEnterWidth, LH), l_PMStr);
     end;//not First
     MoveWindowOrg(l3PointX(-D));
     if l_IsHyperlink then DrawArrow(LH);
    end;//First...
   end;//AddHyphen
  end;//with Area.rCanvas
 end;//DrawIndent

var
 WC            : Integer;   {- количество слов в i-й строке}
 DW            : Integer;
 ParaCaretPos  : Integer;
 LineCaretPos  : Integer;
 ParaCaretLine : Integer;
 l_NeedCaret   : Boolean;
 l_Map         : InevLines;
 l_SolidBlock  : Boolean;

 function loc_DrawText(S: Tl3CustomString): Boolean;

  function _TextRect(const S: Tl3PCharLen): Boolean;

   procedure DrawSoftEnter;
   begin//DrawSoftEnter
    with Area.rCanvas do
    begin
     PushFC;
     try
      with Font do
      begin
       Name := def_SymbolFontName;
       Style := [];
      end;//with Font
     finally
      PopFC;
     end;//try..finally
     CaretLineOut(cc_SoftEnterStr, LH, not l_NeedCaret, LineCaretPos);
    end;//with Area.rCanvas
   end;//DrawSoftEnter

  var
   TLen            : Integer;
   l_NeedSoftEnter : Boolean;
   S2Draw          : Tl3PCharLen;
   l_Str           : Tl3PCharLen;
  begin
   with Area.rCanvas do
   begin
    CheckInvertInLeftAAC;
    Result := ((ClipRect.Right > 0) OR (Caret <> nil)) AND HasToDraw;
    if Result then
    begin
     S2Draw := S;
     TLen := l3RTrim(S2Draw).SLen;
     if (TLen > 0) AND (S2Draw.S[Pred(TLen)] = cc_SoftEnter) then
     begin
      l_NeedSoftEnter := DrawSpecial;
      if l_NeedSoftEnter OR (LineCaretPos < 0) OR (LineCaretPos > S2Draw.SLen) then
       Dec(TLen)
      else
      begin
       {-эта ветка необходима для правильного позиционирования курсора
           на конец строки с SoftEnter'ом }
       S2Draw := CheckOutString(S2Draw);
       S2Draw.S[Pred(TLen)] := cc_HardSpace;
       TLen := S2Draw.SLen;
      end;//l_NeedSoftEnter
     end//TLen > 0
     else
     begin
      l_NeedSoftEnter := false;
      TLen := S2Draw.SLen;
     end;//TLen > 0
     l_Str := l3PCharLen(S2Draw.S, TLen, S2Draw.SCodePage);
     if DrawSpecial then
      l_Str := CheckConvertString(l_Str);
     CaretLineOut(l_Str, LH, not l_NeedCaret, LineCaretPos);
     if l_NeedSoftEnter then
      DrawSoftEnter;
    end;//Result
   end;//with Area.rCanvas
  end;//_TextRect

  function DrawAllLine: Boolean;
    {-нарисовать всю строку, без выделения}
  begin//DrawAllLine
   Result := _TextRect(S.AsPCharLen);
   S.Offset(S.Len);
  end;//DrawAllLine

 var
  D2S : Integer; {-ширина пустого места}

  procedure GetD2S;
  begin//GetD2S
   if (WC > 0) then
   begin
    {if (DW < 0) then begin
     D2S := 0;
     DW := 0;
    end else }begin
     if (WC = 1) then
      D2S := DW
     else
      D2S := DW div WC;
     Dec(WC);
     with Area.rCanvas do
     begin
      D2S := LP2DP(l3PointX(D2S)).X;
      Dec(DW, DP2LP(PointX(D2S)).X);
     end;//with Area.rCanvas
    end;//DW < 0
   end
   else
    D2S := 0;
  end;//GetD2S

 var
  l_InDrawBlock      : Boolean;
  l_NeedCorrectCaret : Boolean;
  l_pxLH             : Integer;
  l_NS               : Tl3CustomString;
 begin//loc_DrawText
  Result := true;
  with Area.rCanvas do
  begin
   if S.Empty then
   begin
    if (LineCaretPos = 0) then
    begin
     SetCaret(l3Point0, l3Point(AverageCharWidth, LH), not l_NeedCaret);
     ParaCaretPos := -1;
     LineCaretPos := -1;
     ShowCursor := false;
    end;//LineCaretPos = 0
   end//S.Empty
   else
   begin
    if (l_Justification <> ev_itWidth) OR
       not l3HasWhiteSpace(S.AsPCharLen) then
    begin
     {-проверяем нужна ли разгонка и есть ли в строке пробелы}
     if l_SolidBlock then
      Result := DrawAllLine
     else
      f_LinePainter.DrawF(As_Il3Canvas, evL2DLA(@_TextRect), S);
    end//l_Justification <> ev_itWidth
    else
    begin
     {-рисуем разогнанную строку}
     l_pxLH := LP2DP(l3PointY(LH)).Y;
     l_NS := S.Clone;
     try
      while Result AND not S.Empty do
      begin
       S.FindCharEx(cc_HardSpace, l_NS);
       l_NeedCorrectCaret := (LineCaretPos = S.Len);
       if l_SolidBlock then
       begin
        Result := DrawAllLine;
        l_InDrawBlock := false;
       end//l_SolidBlock
       else
        l_InDrawBlock := Boolean(f_LinePainter.DrawF(As_Il3Canvas, evL2DLA(@_TextRect), S));
       if Result AND l3IsWhiteSpaceS(S.AsPCharLen, -1) then
       begin
        if l_InDrawBlock then
        begin
         BeginInvert;
         CheckInvertInLeftAAC;
        end;
        try
         GetD2S;
         FillRect(l3SRect(0, 0, D2S, l_pxLH));
         MoveWindowOrg(PointX(-D2S));{-сдвигаем курсор отрисовки на ширину пустого места}
         if l_NeedCorrectCaret then
          IncCaret(D2S - 1);
         Result := (ClipRect.Right > 0) AND HasToDraw;
        finally
         if l_InDrawBlock then
          EndInvert;
        end;//try..finally
       end;//Result AND evWhiteSpace
       S.AssignString(l_NS);
      end;//while Result AND not S.Empty
     finally
      l3Free(l_NS);
     end;//try..finally
    end;//l_Justification <> ev_itWidth
   end;//S.Empty
   Result := Result AND (ClipRect.Right > 0) AND HasToDraw;
  end;//with Area.rCanvas
 end;//loc_DrawText

var
 FirstLineWidth : Integer;
 OtherLineWidth : Integer;
 l_LineOffsetX  : Integer;
 l_ExtentX      : Integer;
 l_LineCount    : Integer;
 l_ActiveElement : InevActiveElement;

 procedure DrawPara;

  function DrawLine(ppLI: PPevLineInfo; LID: Integer): Boolean;
  var
   l_SegmentsList : Ik2TagList;

   function DrawSegment(const aSeg: InevTag; Index: Integer): Boolean;

   var
    l_OM : TOutlineTextMetric;
    l_Pnt : IevPainter;
    l_DrawingObject : InevTag;
    
    function DrawObject(const S: Tl3PCharLen): Boolean;
    var
     l_Ex     : Tl3Point;
     l_Obj    : InevObject;
     l_SegInf : TnevShapeInfo;
     l_Points : TnevShapePoints;
     l_Point  : InevPoint;
     l_BL     : Integer;
    begin//DrawObject
     Result := true;
     with Area.rCanvas do
     begin
      l_Ex := TextExtent(S);
      PushWO;
      try
       l_BL := aSeg.IntA[k2_tiBaseLine];
       if not l_DrawingObject.InheritsFrom(k2_idBitmapPara) then
       // http://mdp.garant.ru/pages/viewpage.action?pageId=174295160&focusedCommentId=344137422#comment-344137422
       // - пока так
        MoveWindowOrg(l3PointY(-((LH - l_Ex.Y) div 2)))
       else
       begin
 {       if (l_OM.otmDescent <> 0) then
         if (l_OM.otmDescent <> 1) then
          l_BL := l_BL + Area.rCanvas.DP2LP(PointY(l_OM.otmDescent - 1)).Y;}
        if (l_OM.otmDescent <> 0) then
          l_BL := l_BL + Area.rCanvas.DP2LP(PointY(l_OM.otmDescent)).Y;
        MoveWindowOrg(l3PointY(-((LH - l_Ex.Y))));
       end;//not l_DrawingObject.InheritsFrom(k2_idBitmapPara)
       if (l_BL <> 0) then
        MoveWindowOrg(l3PointY(-l_BL));
       l3FillChar(l_Points, SizeOf(l_Points));
       l_Points.rTop := nil;
       l_Points.rCaret := nil;
       l_Points.rSelection := nil;
       l_Points.rPrevHeight := 0;
       l_Points.rRealTop := nil;
       l_Points.rFake := False;
       l_Points.rCheckCaret := nil;
       l_Points.rCheckSelection := nil;
       if (LineCaretPos > 0) then
        Dec(LineCaretPos, S.SLen);
       if (SelRange <> nil) then
       begin
        l_Point := ParaX.MakePoint;
        l_Point.PositionW := aSeg.IntA[k2_tiStart];
        if SelRange.Contains(Area.rView.As_InevView, l_Point) then
         if l_DrawingObject.QT(InevObject, l_Obj) then
          l_Points.rSelection := l_Obj.Range;
       end;//SelRange <> nil
 (*           if (LineCaretPos = 0) AND QT(InevObject, l_Obj) then
        l_Pnt.Draw(nil, Area.rCanvas, nil, l_Obj.Point, nil, l_SegMap, High(Integer))
       else*)
        l_Pnt.Draw(Area^, l_Points, l_SegInf);
      finally
       PopWO;
      end;//try..finally
      MoveWindowOrg(l3PointX(-l_Ex.X));
      MoveWindowOrg(PointX(-1));
     end;//with Area.rCanvas
    end;//DrawObject

   var
    l_SegFont  : Il3Font;
    l_BC       : Tl3Color;
    l_ActiveHyperlinkStyle : InevTag;
    l_AH                   : InevActiveElement;
    l_CheckedS1 : Tl3CustomString; 
    l_CheckedS2 : Tl3CustomString;
    l_CheckedS3 : Tl3CustomString;
   begin//DrawSegment
    l_CheckedS1 := CheckS(1);
    l_CheckedS2 := CheckS(2);
    l_CheckedS3 := CheckS(3);
    evTextParaGetLineSegment(l_Map, l_ParaS, Succ(LID), Index, l_SegmentsList,
                             l_CheckedS1, l_CheckedS2, l_CheckedS3
                             {CheckS(1), CheckS(2), CheckS(3)});
    Result := loc_DrawText(l_CheckedS1{S[1]});
    if Result then
    begin
     with Area.rCanvas do
     begin
      Push;
      try
       l_SegFont := FormatInfo.InfoForChild(TnevSegmentObject.Make(aSeg)).Font[false];
       //l_SegFont := ETAOIN_SHRDLU_GetObjIFont(Area.rView.Metrics, aSeg, true, FormatInfo^, false);
       if l_SegFont.IsAtomic then
       begin
        l3FillChar(l_OM, SizeOf(l_OM));
        if (GetOutlineTextMetrics(Area.rCanvas.DC, SizeOf(l_OM), @l_OM) = 0) then
         Assert(false, 'Не удалось получить метрики текущего шрифта канвы');
       end;//l_SegFont.IsAtomic
       l_BC := BackColor;
       Font := l_SegFont;
       // - устанавливаем шрифт сегмента
       if (l_ActiveElement <> nil) then
       begin
        l_AH := TnevActiveHyperlink.Make(ParaX, aSeg);
        if ParaX.IsDecorationElement then
         l_AH := TnevDecorActiveHyperlink.Make(ParentPainter.Obj, l_AH);
        //if aSeg.InheritsFrom(k2_idHyperlink) then // - это убрано, чтобы корректно продолжения ссылок подчёркивались
         if l_ActiveElement.IsSame(l_AH) then
         begin
          l_ActiveHyperlinkStyle := k2.TypeTable.ObjToTag(k2.TypeTable[k2_idTextStyle].ValueTable.DRByID[evd_saActiveHyperLink]);
          if l_ActiveHyperlinkStyle.IsValid then
          begin
           if l_ActiveHyperlinkStyle.HasSubAtom(k2_tiFont) then
           begin
            with l_ActiveHyperlinkStyle.Attr[k2_tiFont] do
            begin
             with Font do
             begin
              if HasSubAtom(k2_tiUnderline) then
               Underline := BoolA[k2_tiUnderline];
              if HasSubAtom(k2_tiBold) then
               Bold := BoolA[k2_tiBold];
              if HasSubAtom(k2_tiItalic) then
               Italic := BoolA[k2_tiItalic];
              if HasSubAtom(k2_tiStrikeout) then
               Strikeout := BoolA[k2_tiStrikeout];
              if HasSubAtom(k2_tiForeColor) then
               ForeColor := IntA[k2_tiForeColor];
              if HasSubAtom(k2_tiBackColor) then
               BackColor := IntA[k2_tiBackColor];
             end;//Font
            end;//with l_ActiveHyperlinkStyle.Attr[k2_tiFont]
           end;//l_ActiveHyperlinkStyle.HasSybAtom(k2_iFont)
          end;//l_ActiveHyperlinkStyle.IsValid
         end;//l_ActiveElement.IsSame(TnevActiveHyperlink.Make(ParaX, aSeg))
       end;//l_ActiveElement <> nil
       if (l_SegFont.BackColor = nevDefaultColor) AND
          aSeg.HasSubAtom(k2_tiVisible) then
        if aSeg.BoolA[k2_tiVisible] then
         if not ParaX.BoolA[k2_tiVisible] then
         // - принудительно выставляем цвет фона для видимых сегментов
          BackColor := l_BC;
       ValidateSegFont(aSeg);
       if l_SegFont.IsAtomic then
       begin
        l_DrawingObject := aSeg.Child[0];
        if l_DrawingObject.QT(IevPainter, l_Pnt) then
         try
          l_Pnt.ParentPainter := Self;
          f_LinePainter.DrawF(As_Il3Canvas, evL2DLA(@DrawObject), l_CheckedS2{S[2]});
          Result := true;
         finally
          l_Pnt := nil;
         end//try..finally
        else
         Result := loc_DrawText(l_CheckedS2{S[2]});
       end//l_SegFont.IsAtomic
       else
        Result := loc_DrawText(l_CheckedS2{S[2]});
      finally
       Pop;
      end;//try..finally
      if Result then
      begin
       Font := l_ParaFont;
       // - возвращаем шрифт параграфа
       ValidateParaFont;
       Result := loc_DrawText(l_CheckedS3{S[3]});
      end;//Result
     end;//with Area.rCanvas
    end;//Result
   end;//DrawSegment
                                   
   procedure DrawBullet;
   const
    cc_BulletStr : Tl3PCharLenConst = (S         : #$B7;
                                       SLen      : 1;
                                       SCodePage : CP_ANSI);
   var
    l_BulletWidth : Integer;
    l_Bullet      : Integer;
   begin//DrawBullet
    l_Bullet := ParaX.IntA[k2_tiBullet];
    if (l_Bullet > 0) then
    begin
     // - bullet присутствует
     with Area.rCanvas do
     begin
      Font.Name := def_SymbolFontName;
      l_BulletWidth := TextExtent(cc_BulletStr).X;
      MoveWindowOrg(l3PointX(2 * l_BulletWidth));
      ExtTextOut(l3Point0, l3Rect(0, 0, l_BulletWidth, LH), cc_BulletStr);
      MoveWindowOrg(l3PointX(-2 * l_BulletWidth));
      Font := l_ParaFont;
      //- восстанавливаем шрифт параграфа
      ValidateParaFont;
     end;//with Area.rCanvas
    end;//l_Bullet > 0
   end;//DrawBullet
   
  var
   LeftIndent   : Integer;
   MaxLineWidth : Integer;
   IsLastLine   : Boolean;
   l_LineLength : Integer;
   l_TabInfo    : Il3TabInfo;
   l_SaveOrg    : Tl3Point;
   l_TabIndent  : Integer;
   l_CheckedS1  : Tl3CustomString;
  begin//DrawLine
   Result := true;
   l_TabIndent := 0;
   with Area.rCanvas do
   begin
    StartTabs(l_TabInfo, ParaX.TabStops);
    try
     try
      if (l_LineOffsetX >= 0) then
      begin
       l_SaveOrg := WindowOrg;
       try
        LH := ppLI^^.LE.Y;
        Dec(l_SaveOrg.P.Y, LH);
        {-сдвигаем курсор отрисовки на высоту строки}
        l_LineLength := ppLI^^.B - l_LineOffsetX;
        {-вычисляем длину строки}
        if ShowCursor AND (ParaCaretPos >= l_LineOffsetX) AND
           (LID = ParaCaretLine) then
         LineCaretPos := ParaCaretPos - l_LineOffsetX
        else
         LineCaretPos := -1;
        if (ClipRect.Top >= LH) and ((Caret = nil) or not ShowCursor) then
         Exit;
        if not DrawRgnOrBlock AND
           ((LineCaretPos < 0) OR (LineCaretPos > l_LineLength)) then
         Exit;
        f_LinePainter.StartLine(l_LineOffsetX, l_LineLength);
        Font := l_ParaFont;
        if (LID > 0) then
        begin
         MaxLineWidth := OtherLineWidth;
         LeftIndent := Spaces.Left;
        end//LID > 0
        else
        begin
         MaxLineWidth := FirstLineWidth;
         LeftIndent := f_FirstIndent;
         if (ParentPainter <> nil) and
            (ParentPainter.ParentPainter <> nil) and
            (ParentPainter.ParentPainter.TableRowPainter <> nil) then
         begin
          l_TabIndent := evCalcDecimalTabIndent(ParaX, l_ParaS, l_Justification, l_LineCount, As_Il3Canvas);
          Inc(LeftIndent, l_TabIndent);
         end; // if (ParaMarkStyle = ev_pmCell) and (ParaX.TabStops <> nil) then
        end;//(LID > 0)
        IsLastLine := (LID = l_LineCount);
        if IsLastLine AND (l_Justification = ev_itWidth) then
         l_Justification := ev_itLeft;
        FontIndexSet := ppLI^^.FI;
        WC := ppLI^^.WC;
        if Printing then
        begin
         if (ClipRect.Bottom - LH) <= 5 * l3Epsilon then
         // - не нужно ли начать новую страницу
         begin
          Result := false;
          // - не нужно оставшиеся строки печатать
          DrawLeaf := false;
          // -недопечатали...
          Inc(l_SaveOrg.P.Y, LH);
          // - учитываем недопечатанную строчку
          Exit;
          // -выходим
         end;//ClipRect.Bottom < LH
        end;//Printing
        DW := MaxLineWidth - ppLI^^.LE.X - l_TabIndent;
        Assert((l_Justification = ev_itPreformatted) OR (DW < High(Integer) div 4), 'Значение ширины похоже на мусор');
        l_SegmentsList := evTextParaGetLineSegments(FormatInfo, l_ParaS, ParaX, Succ(LID));
        MoveWindowOrg(l3PointX(-LeftIndent));
        // - сдвигаем курсор отрисовки на ширину отступа
        DrawIndent(true, DW, IsLastLine, false);
        // - рисуем левый отступ
        //Font := l_ParaFont;
        // - устанавливаем шрифт параграфа
        ValidateParaFont;
        if (LID = 0) then
         DrawBullet;
         // - рисуем маркер списка
        if l_SegmentsList.Empty then
        begin
         l_CheckedS1 := CheckS(1);
         with l_CheckedS1 do
          if (l_ParaS.S = nil) then
           Clear
          else
           AsPCharLen := l3PCharLenPart(l_ParaS.S,
                                        l_LineOffsetX, l_LineOffsetX + l_LineLength,
                                        l_ParaS.SCodePage);
         loc_DrawText(l_CheckedS1{S[1]});
        end//l_SegmentsList.Empty
        else
        begin
         CheckDrawFocused;
         l_SegmentsList.ForEachF(k2L2TIA(@DrawSegment));
        end;
        DrawIndent(false, DW, IsLastLine, ppLI^^.AddHyphen);
        {-рисуем правый отступ и символ конца параграфа}
       finally
        WindowOrg := l_SaveOrg;
        if (ClipRect.Bottom <= 0) then
         Result := false;
        // -если уперлись вниз экрана то ВЫХОД
        Result := Result AND HasToDraw;
        // - еще осталось что рисовать
       end;//try..finally
      end;//l_LineOffsetX >= 0
     finally
      l_LineOffsetX := ppLI^^.B;
      // - запоминаем конец текущей строки
     end;//try..finally
    finally
     FinishTabs(l_TabInfo);
    end;//try..finally
   end;//with Area.rCanvas
  end;//DrawLine

 var
  IA : Tl3IteratorAction;
 begin//DrawPara
  l_Justification := TevIndentType(ParaX.IntA[k2_tiJustification]);
  with Area.rCanvas do
  begin
   if not ParaX.TreatCollapsedAsHidden OR not Area.rView.IsObjectCollapsed[ParaX] then
   begin
    if (l_Justification = ev_itWidth) AND IsVirtual then
     l_Justification := ev_itLeft;
    f_FirstIndent := FormatInfo.FirstIndent;
    l_LineCount := Pred(evTextParaLineCount(FormatInfo));
    FirstLineWidth := l_ExtentX - f_FirstIndent;
    OtherLineWidth := l_ExtentX - Spaces.Left;
    Push;
    try
     l_ParaFont := FormatInfo.Font[false];
     //l_ParaFont := ETAOIN_SHRDLU_GetObjIFont(Area.rView.Metrics, ParaX, false, FormatInfo^, false);
     // -вычисляем шрифт параграфа
     try
      IA := l3L2IA(@DrawLine);
      try
       if (l_LineOffsetX >= 0) then
        evTextParaIterateLines(FormatInfo, l_ParaS, ParaX, IA, Max(0, Pred(f_TopLine)))
       else
        evTextParaIterateLines(FormatInfo, l_ParaS, ParaX, IA, Max(0, f_TopLine - 2));
      finally
       l3FreeIA(IA);
      end;//try..finally
     finally
      l_ParaFont := nil;
     end;//try..finally
    finally
     Pop;
    end;//try..finally
   end//not ParaX.TreatCollapsedAsHidden OR not Area.rView.IsObjectCollapsed[ParaX]
   else
    Result := true;
   if Result then
    MoveWindowOrg(l3PointY(-Spaces.Bottom));
    // - учитываем отступ после абзаца
  end;//with Area.rCanvas
 end;//DrawPara

//#UC END# *4804C81000B9_49DA30D901B6_var*
begin
//#UC START# *4804C81000B9_49DA30D901B6_impl*
 StartDrawingInitFields;
 f_HeaderHeight := 0;
 l_IsHyperlink := EvIsParaHyperlink(ParaX);
 Result := true;
 l_ActiveElement := Area.rView.ActiveElement;
 l_Map := FormatInfo.Lines;
 l_ExtentX := Max(FormatInfo.rLimitWidth - Spaces.Right, FormatInfo.Width);
 f_TopLine := Succ(TopAnchor.VertPosition(Area.rView.As_InevView, FormatInfo));
 if (Caret <> nil) AND Area.rView.ForceDrawFocusRect then
  f_ForceFocusRect := true;
 with Area.rCanvas do
 begin
  PushWO;
  try
   if Result AND (f_TopLine = 1) then
    MoveWindowOrg(l3PointY(-Spaces.Top));
    // - учитываем отступ перед абзацем
   if not Printing OR (ClipRect.Bottom > 0) then
   begin
    l_ParaS := ParaX.Text;
    if ShowCursor AND (Caret <> nil) then
    begin
     l_NeedCaret := Caret.NeedWindowsCaret;
     l_LineOffsetX := evTextParaGetPosByLine(l_Map, l_ParaS, f_TopLine);
     ParaCaretPos := Caret.Position;
     ParaCaretLine := Caret.VertPosition(Area.rView.As_InevView, FormatInfo);
    end//ShowCursor
    else
    begin
     l_NeedCaret := false;
     if (f_TopLine = 1) then
      l_LineOffsetX := 0
     else
      l_LineOffsetX := -1;
     ParaCaretPos := -1;
    end;//ShowCursor
    if (f_LinePainter = nil) then
     f_LinePainter := TevLinePainter.Create;
    f_LinePainter.StartPara(Area.rView.As_InevView, SelRange);
    try
     l_SolidBlock := (SelRange = nil);
     if l_SolidBlock then
      DrawPara
     else
     begin
      l_SolidBlock := SelRange.Solid(Area.rView.As_InevView);
      if l_SolidBlock then
      begin
       if (l_ParaS.S = nil) then
        DrawPara
       else
       begin
        BeginInvert;
        try
         DrawPara;
        finally
         EndInvert;
        end;{try..finally}
       end;//l_ParaS.S = nil
      end//l_SolidBlock
      else
       DrawPara;
     end;//l_SolidBlock
    finally
     f_LinePainter.FinishPara;
    end;//try..finally
   end;//not Printing OR (ClipRect.Bottom >= 0
  finally
   PopWO;
  end;//try..finally
 end;//with Area.rCanvas
//#UC END# *4804C81000B9_49DA30D901B6_impl*
end;//TevTextParaPainterEx.DrawLeaf

procedure TevTextParaPainterEx.FillUnfilled(const aRect: Tl3Rect);
//#UC START# *4E2702FE01EA_49DA30D901B6_var*
const
 cRad = 12;
 cArrow = 10;
 cFW = 1;
var
 l_Rgn  : Tl3Region;
 l_Rgn1 : Tl3Region;
 l_Rgn2 : Il3Region;
 l_R    : Tl3Rect;
 l_SR   : Tl3SRect;
 l_PointArray : array of TPoint;
 l_Med : Integer;
 //l_BC : TColor;
 l_NeedTriangle : Boolean;
//#UC END# *4E2702FE01EA_49DA30D901B6_var*
begin
//#UC START# *4E2702FE01EA_49DA30D901B6_impl*
 if ParaX.Attr[k2_tiStyle].BoolA[k2_tiCollapsable] AND
    (
     (ParaX.BackColor <> nevDefaultColor)
(*     (ParaX.IntA[k2_tiBackColor] <> nevDefaultColor) OR
     (ParaX.Attr[k2_tiStyle].Attr[k2_tiFont].IntA[k2_tiBackColor] <> nevDefaultColor)*)
    ) AND
    not Area.rView.IsObjectCollapsed[ParaX] AND
    Area.rCanvas.DrawEnabled and not Area.rCanvas.IsVirtual then
 begin
(*  l_BC := ParaX.IntA[k2_tiBackColor];
  if (l_BC = nevDefaultColor) then
  begin
   l_BC := ParaX.Attr[k2_tiStyle].Attr[k2_tiFont].IntA[k2_tiBackColor];
   if (l_BC = nevDefaultColor) then
   begin
    inherited;
    Exit;
   end;//l_BC = nevDefaultColor
  end;//l_BC = nevDefaultColor*)
  with Area.rCanvas do
  begin
   l_Rgn := Tl3Region.Create;
   try
    PushClipRect;
    try
     l_R := aRect;
     if (f_TopLine = 1) then
      l_R.Top := l_R.Top + f_HeaderHeight
     else
      l_R.Top := l_R.Top - f_HeaderHeight;
     l_SR := LR2DR(l_R);
     with l_SR do
      l_Rgn.Rgn := CreateRoundRectRgn(Left, Top, Right, Bottom, cRad, cRad);
     ClipRegion := l_Rgn;
     inherited;
    finally
     PopClipRect;
    end;//try..finally
    PushClipRect;
    try
     l_Rgn1 := Tl3Region.Create;
     try
      l_NeedTriangle := (f_TopLine = 1) AND (f_HeaderHeight <> 0);
      if l_NeedTriangle then
      begin
       SetLength(l_PointArray, 3);
       l_Med := l_SR.Left + (l_SR.Right - l_SR.Left) div 2;
       l_PointArray[0] := Point(l_Med - cArrow, l_SR.Top);
       l_PointArray[1] := Point(l_Med + cArrow, l_SR.Top);
       l_PointArray[2] := Point(l_Med, l_SR.Top - cArrow);
       l_Rgn1.Rgn := Windows.CreatePolygonRgn(l_PointArray[0],
                                              Length(l_PointArray),
                                              ALTERNATE{WINDING});
       ClipRegion := l_Rgn1;
       FillRgn(l_Rgn1);
      end;//f_TopLine = 1
      l_Rgn2 := l_Rgn.Clone;
      if l_NeedTriangle then
       l_Rgn2.Combine(l_Rgn1, RGN_OR);
      ClipRegion := l_Rgn2;
      Push;
      try
       Canvas.Brush.Color := clSilver;
       FrameRgn(DC, l_Rgn2.Rgn, Canvas.Brush.Handle, cFW, cFW);
      finally
       Pop;
      end;//try..finally
     finally
      FreeAndNil(l_Rgn1);
     end;//try..finally
    finally
     PopClipRect;
    end;//try..finally
    PushClipRect;
    try
     BackColor := TopBC;
     //BackColor := l_BC;
     l_Rgn1 := Tl3Region.Create;
     try
      l_Rgn1.Rect := LR2DR(l_R);
      l_Rgn1.Combine(l_Rgn, RGN_DIFF);
      ClipRegion := l_Rgn1;
      FillRect(l_R);
     finally
      FreeAndNil(l_Rgn1);
     end;//try..finally
    finally
     PopClipRect;
    end;//try..finally
   finally
    FreeAndNil(l_Rgn);
   end;//try..finally
  end;//with Area.rCanvas
 end//ParaX.Attr[k2_tiStyle].BoolA[k2_tiCollapsable]
 else
  if not SpecialFill(aRect, False) then
   inherited;
//#UC END# *4E2702FE01EA_49DA30D901B6_impl*

end;//TevTextParaPainterEx.FillUnfilled