Docu/ru/Tut-5.odc

5 Тексты
ENGLISH

    Графические интерфейсы пользователя ввели графические элементы в повседневную компьютерную жизнь. Это значительный шаг вперед в сравнении с модальным текстовым интерфейсом, но простой текст нисколько не устарел. Даже в отчетах с большим количеством иллюстраций текст служит необходимым связующим звеном.
    В типичном приложении баз данных не выполняются операции над графической информацией. Данные, которые вводятся и выбираются, в большинстве своем являются текстом, и отчеты обычно печатаются в виде текста, оформленного в таблицы.
    В виду важности текста в этой главе описывается как можно использовать текстовые абстракции BlackBox Component Builder для различных целей. В нескольких примерах будет использован модуль простейшей базы данных, представленный в предыдущей главе.
     Работа будет осуществляться "снизу-вверх" (bottom-up), на примерах будет показано вначале как записать новый текст, затем прочитать имеющийся текст и в конце как изменить имеющийся текст. В процессе обсуждения примеров встретятся текстовые модели/носители, курьеры, отображатели и дополнительные абстракции, такие как шрифты, атрибуты, линейки и др.


    5.1 Запись текста

    В BlackBox Component Builder'е тексты используются в рамках схемы проектирования носитель-курьер-отображатель (см. главу 3). Они обеспечивает текстовые модели в качестве носителей для текста, текстовых записывателей и считывателей в качестве курьеров для текстовых моделей. Оба типа описаны в центральном модуле TextModels подсистемы Text. Записыватель текста хранит текущую позицию и текущие атрибуты, которые будут использованы при записи очередного элемента в текст. Ниже приведено полное определение записывателя:

	Writer = POINTER TO ABSTRACT RECORD
		attr-: TextModels.Attributes;
		(wr: Writer) Base (): TextModels.Model, NEW, ABSTRACT;
		(wr: Writer) Pos (): INTEGER, NEW, ABSTRACT;
		(wr: Writer) SetPos (pos: INTEGER), NEW, ABSTRACT;
		(wr: Writer) SetAttr (attr: TextModels.Attributes), NEW;
		(wr: Writer) WriteChar (ch: CHAR), NEW, ABSTRACT;
		(wr: Writer) WriteView (view: Views.View; w, h: INTEGER), NEW, ABSTRACT
	END;

Листинг 5-1. Определение типа TextModels.Writer
    
    Наиболее важной процедурой записывателя является WriteChar, она позволяет записать новый символ в текущую позицию. Если позиция символа находится внутри текста (т.е. в диапазоне 0..text.Length() - 1), символ вставляется на эту позицию в тексте. (Это отличается от абстракции файла BlackBox'а, в которой в данном случае старые данные будут переписаны записывателем. В случае текстов записыватели всегда вставляют, но не перезаписывают.) Если записыватель указывает на позицию в конце текста (т.е. text.Length()), символ дописывается к тексту, что сделает текст на один элемент длиннее. После выполнения процедуры WriteChar позиция записывателя в тексте увеличивается на единицу. Текущую позицию записывателя можно узнать, вызвав функцию записывателя Pos, и ее можно изменить, вызвав процедуру SetPos.

    В связи с тем, что модель текста может содержать отображения (view) наряду с обычными символами, имеется процедура WriteView, позволяющая записать отображение с конкретными указанными размерами (w для ширины, h для высоты). Как и большинство геометрических размеров в BlackBox, ширина и высота отображения определяется в так называемых универсальных единицах, составляющих 1/36000 мм. Это значение выбрано для исключения ошибок округления для большинства разрешений экрана и принтера. Например, дюйм, 1/300 часть дюйма, миллиметр и полиграфический пункт могут быть выражены в универсальных единицах без ошибок округления. Если не указано иное, предполагается, что размеры выражены в универсальных единицах. 
    Если не требуется указывать определенный размер для отображения, то передаются значение Views.undefined для ширины и/или высоты, что позволяет тексту выбрать подходящий размер для записываемого отображения.

    Как создается записыватель? Как и все курьеры, записыватели создаются с помощью фабричной функции их носителей. Для этой цели текстовая модель предоставляет процедуру NewWriter, которая создает записывателя и соединяет его с текстом. Записыватель может вернуть текст посредством функции Base. Вновь созданный записыватель позиционируется на конец текста.

    Элемент текста содержит не только собственно символ, но также и атрибуты символа. В поле attr записывателя содержатся атрибуты, которые будут применяться при записи следующего символа или отображения. Текущие атрибуты могут быть изменены вызовом процедуры записывателя SetAttr.

    В качестве атрибутов текстовая подсистема BlackBox Component Builder поддерживает цвет и вертикальное смещение. Цвета закодированы целыми числами (Ports.Color). Модуль Ports содержит предопределенные константы black (черный), grey6 (серый 6), grey12 (серый 12), grey25, grey50, grey75, white (белый), red (красный), green (зеленый) и blue (синий). "grey6" означает смесь 6% белого и 94% черного, т.е. почти черный. Другие значения серого постепенно становятся все светлее. Вертикальное смещение измеряется в универсальных единицах. 

    Помимо цвета и вертикального смещения текстовые элементы также имеют атрибут шрифта. Шрифт - это коллекция изображений. Изображения представляют собой визуальное отображение кода символа. Разные шрифты могут содержать различные изображения для одного и того же символа. Например, Helvetica выглядит иначе, чем Times или Futiger. Шрифты могут поддерживать не все коды символов, представленных в Component Pascal. Это не удивительно, так как тип CHAR для Component Pascal соответствует стандарту Unicode. Unicode - 16-ти битный стандарт, что означает возможность определения более 65000 различных символов. Первый 256 символов - символы из Latin-1. Первые 128 символов из Latin-1 - наиболее значимые символы набора ASCII.
    Если в шрифте не определено какое-либо изображение, каркас BlackBox'а вернет изображение этого символа из какого-нибудь другого шрифта, либо  символ "отсутствующее изображение", т.е. маленький пустой прямоугольник. Объекты шрифта определены в модуле Fonts. Сейчас нет необходимости более подробно рассматривать этот модуль здесь, достаточно интерпретировать указатель типа Fonts.Font как идентификатор штифта с какими-то конкретными значениями начертания, стиля написания, жирности и размера.

    Начертание (typeface)
    Начертание определяется группой шрифтов, к которой он принадлежит, оно определяет внешний вид для всей группы в целом. Примеры начертаний: Helvetica, Times, Courier. Наименования начертаний представлены в виде символьного массива типа Fonts.Typeface. 
    
    Стиль написания (Style)
    Штифт дополнительно может быть написан наклонно, подчеркнут, зачеркнут или содержать все эти комбинации. Стили написания представлены в виде множества. На данный момент определены элементы Fonts.italic, Fonts.underline и Fonts.strikeout.

    Жирность (weight)
    Шрифты могут быть разной жирности, то есть в диапазоне от светлого к нормальному и от полужирного к жирному. Обычно шрифт бывает нормальным или полужирным. Жирность определяется как целое число. Имеются предопределенные значения Fonts.normal и Fonts.bold.

    Размер (size)
    Шрифты могут отображаться различными размерами, в зависимости от разрешения применяемого устройства вывода. Часто размер задается в пунктах. Пункт  1/72 часть дюйма. В BlackBox размер шрифта измеряется в универсальных единицах, которые подобраны так, что пункты (Ports.point) и другие полиграфические единицы могут быть представлены без ошибок округления. Обычные размеры шрифта: 10 или 12 пунктов.
(10 * Ports.point or 12 * Ports.point)

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

    Атрибуты текста: шрифт, цвет, вертикальное смещение   собраны в объекте типа TextModels.Attributes. Слегка упрощая, этот тип определен как:

Attributes = POINTER TO RECORD (Stores.Store)
		color-: Ports.Color;
		font-: Fonts.Font;
		offset-: INTEGER
	END;

Листинг 5-2. Упрощенное определение TextModels.Attributes

    Это - тип поля attr записывателя. Он является расширением типа Stores.Store. Это означает, что подобный объект можно сохранять в файл. Другие примеры хранимых объектов: TextModels.Model и TextViews.View, но не TextModels.Writer. Более детальное описание приведено в части III. 
    Сейчас рассмотрим возможности записывателя текстов, включая поддерживаемые атрибуты текста. Типичное использование писателей текста приведено в нижеследующем образце кода. Образец кода - выдержка из процедуры, показывающий один или несколько важных аспектов обсуждаемых объектов. Подобный фрагмент кода является рецептом, который должен знать владеющий этой темой программист. 

VAR t: TextModels.Model; wr: TextModels.Writer; ch: CHAR;
	BEGIN
		t := TextModels.dir.New();	(* разместим новую пустую текстовую модель*)
		wr := t.NewWriter(NIL);
		... produce ch ...
		WHILE condition DO
			wr.WriteChar(ch);
			... produce ch ...
		END;

Листинг 5-3. Образец кода из TextModels.Writer

    Фрагмент кода не очень интересен, потому что не показывает, каким образом сгенерированный текст можно отобразить на экране. Объект текста представляет лишь текст с его атрибутами. Он не знает о том, как изобразить этот текст, даже не знает, как его форматировать; т.е. не знает каким образом разбить текст на строки и страницы так, чтобы они уместились в заданном прямоугольнике (отображении, листе бумаги и т.п.). Когда необходимо отобразить текст, нужно обеспечить отображение текста для текстового объекта. Объекты данных, которые могут быть представлены в нескольких отображениях, обычно называются моделями (models). Отделение модели от отображения  важная проектная схема, которая обсуждалась в главе 2.

    Модуль TextModels только экспортирует абстрактный тип записей для текстовых моделей без конкретной реализации. Взамен он экспортирует объект директория (TextModels.dir), который обеспечивает необходимую фабричную функцию (TextModels.dir.New). Этот косвенный способ создания объекта был обоснован в главе 2.

    Работа непосредственно с записывателем текста неудобна, так как он не поддерживает запись строк. По этой причине нижеследующий пример использует форматеры для записи текстов. Форматер - это отображатель (смотри главу 3), который содержит текстовый записыватель и обеспечивает отображение высокоуровневых символов, таких как символы Component Pascal в поток простых символов, которые могут быть переданы для работы записывателю. В следующем примере применяется форматер для записи текста, очень простой "отчет". Текст представляет собой содержимое базы данных телефонов, по одной строке на запись. Это первая версия отчета по базе данных телефонов реализована в модуле ObxPDBrep0:

MODULE ObxPDBRep0;

	IMPORT Views, TextModels, TextMappers, TextViews, ObxPhoneDB;

	PROCEDURE GenReport*;
		VAR t: TextModels.Model; f: TextMappers.Formatter; v: TextViews.View;
			i: INTEGER; name, number: ObxPhoneDB.String;
	BEGIN
		t := TextModels.dir.New();	(* создание пустого носителя текста *)
		f.ConnectTo(t);					(* соединение фоматера к тексту *)
		i := 0;
		ObxPhoneDB.LookupByIndex(i, name, number);
		WHILE name # "" DO
			f.WriteString(name);		(* первая строка *)
			f.WriteTab;					(* символ табуляции *)
			f.WriteString(number);	(* вторая строка *)
			f.WriteLn;						(* возврат каретки *)
			INC(i);
			ObxPhoneDB.LookupByIndex(i, name, number)
		END;
		v := TextViews.dir.New(t);	(* создание отображения текста для созданного выше текста *)
		Views.OpenView(v)			(* открытие отображения текста в собственном окне *)
	END GenReport;

END ObxPDBRep0.

Листинг 5-4. Запись базы данных телефонов с применением форматера.

    Следует отметить, что имена и телефоны разделены символом табуляции (09X). Так как позиции табуляции не определены для данного текста, то символ табуляции воспринимается как широкий пробел фиксированного размера (ширина которого равна нескольким ширинам символа обычного пробела). Позже будет разъяснено, каким образом можно определить позиции табуляции.

    Результат выполнения команды ObxPDBRep0 приведен на следующем рисунке:


Рисунок 5-5. Окно текстового редактора, содержащее базу данных телефонов.

    Текст во вновь открытом окне представляет собой полноценно редактируемое отображение текста. Его можно редактировать, сохранить как документ BlackBox, распечатать и т.п. Если посмотреть на исходный код, то можно увидеть следующий шаблон кода:

VAR t: TextModels.Model; f: TextMappers.Formatter; v: Views.View;
	BEGIN
		t := TextModels.dir.New();
		f.ConnectTo(t);
		... использование процедур форматера для создания текста ...
		v := TextViews.dir.New(t);
		Views.OpenView(v)

Листинг 5-6. Шаблон кода для TextMappers.Formatter и для открытия текстового отображения.

    Этот шаблон кода встречается во многих командах BlackBox'а, например, DevDebug.ShowLoadedModules. Он удобен во всех случаях, когда нужно написать табличные отчеты различной длины, возможно разделенный на несколько печатных страниц. Так же четко видно, что текст вобрал в себя несколько шаблонов проектирования: объект текста является моделью, которая наблюдается визуальным отображением (проектная схема Наблюдатель), носителем (проектная схема Носитель-Курьер-Отображатель) и контейнером (проектная схема Компоновщик).

    Интересная особенность приведенного выше фрагмента кода: вначале создается текстовый носитель t, затем его содержимое формируется с помощью форматера, и только затем создается отображение v для носителя. В завершение, отображение открывается в собственном окне документа.
    В BlackBox'е важно, что модель может подвергаться изменениям до того, как будет создано какое-либо его отображение. Фактически, намного эффективнее создавать текст до открытия его отображения, потому что обновление экрана и некоторые внутренние ресурсы для обеспечения механизма отката в таком случае не требуется, что заметно сказывается на скорости.
    В BlackBox'е текстовая модель представлена в виде последовательности текстовых элементов. Текстовыми элементами могут быть символы из набора Latin-1 (SHORTCHAR), символы набора Unicode (a CHAR > 0FFX) или отображение (расширение типа Views.View). Текст может иметь атрибуты шрифта, цвета и вертикального смещения. Поскольку модель текста может содержать отображения, то это - контейнер. В следующем примере показано, каким образом данные возможности можно использовать при создании нового текста. База данных телефонов будет использоваться как источник данных для вывода в отчет, при этом будут применяться различные представления для текста в различных примерах.

    Следующий пример показывает каким образом можно генерировать текст из нашей тестовой базы данных. В этом тексте наименования выводятся зеленым цветом. Различия с модулем ObxPDBRep0 выделены подчеркнутым текстом.

MODULE ObxPDBRep1;

	IMPORT Ports, Views, TextModels, TextMappers, TextViews, ObxPhoneDB;

	PROCEDURE GenReport*;
		VAR t: TextModels.Model; f: TextMappers.Formatter; v: TextViews.View;
			i: INTEGER; name, number: ObxPhoneDB.String;
			default,green:TextModels.Attributes;
	BEGIN
		t := TextModels.dir.New();	(* создание пустого носителя *)
		f.ConnectTo(t);					(* соединение форматера с текстом*)
		default:=f.rider.attr;	(* сохранение старых атрибутов для дальнейшего использования *)
		green:=TextModels.NewColor(default,Ports.green);	(* используем зеленый цвет *)
		i := 0;
		ObxPhoneDB.LookupByIndex(i, name, number);
		WHILE name # "" DO
			f.rider.SetAttr(green);	(* поменять текущие атрибуты курьера форматера *)
			f.WriteString(name);		(* первая строка *)
			f.rider.SetAttr(default);	(* поменять текущие атрибуты курьера форматера *)
			f.WriteTab;					(* символ табуляции *)
			f.WriteString(number);	(* вторая строка *)
			f.WriteLn;						(* перевод каретки *)
			INC(i);
			ObxPhoneDB.LookupByIndex(i, name, number)
		END;
		v := TextViews.dir.New(t);	(* создать текстовой отображение для сгенерированного ранее текста  *)
		Views.OpenView(v)			(* открыть отображение в своем собственном окне *)
	END GenReport;

END ObxPDBRep1.

Листинг 5-7. Вывод базы данных телефонов в зеленом цвете

    Следует отметить, что курьер форматера содержит текущий набор атрибутов (TextModels.Writer.attr). Он включает в себя текущий цвет и доступен только для чтения; текущий набор может быть установлен с помощью процедуры записывателя SetAttr.

    Первые примеры: ObxPDBRep0 и ObxPDBRep1 - не выводят красивых отчетов, потому что телефонные номера не выровнены по вертикали один под другим в табличном стиле.
    ObxPDBRep2  исправляет этот дефект, вставив линейку в начало текста. Эта линейка определяет позиции табуляции, которая выровняет все телефонные номера, отделенные от имен знаком табуляции. Отличия от модуля ObxPDBRep0 помечены подчеркнутым текстом.

MODULE ObxPDBRep2;

	IMPORT Ports, Views, TextModels, TextMappers, TextViews, TextRulers, ObxPhoneDB;

	PROCEDUREWriteRuler(VARf:TextMappers.Formatter);
		CONST cm = 10 * Ports.mm;	(* универсальные определения *)
		VAR ruler: TextRulers.Ruler;
	BEGIN
		ruler := TextRulers.dir.New(NIL);
		TextRulers.AddTab(ruler, 4 * cm);	(* определить остановку табуляции, 4 cm от левого отступа *)
		TextRulers.SetRight(ruler, 12 * cm);	(* определить правый отступ *)
		f.WriteView(ruler)							(* линейка - это отображение, оно может быть записано в текст *)
	END WriteRuler;

	PROCEDURE GenReport*;
		VAR t: TextModels.Model; f: TextMappers.Formatter; v: TextViews.View;
			i: INTEGER; name, number: ObxPhoneDB.String;
	BEGIN
		t := TextModels.dir.New();	(* создать пустого текстового носителя *)
		f.ConnectTo(t);					(* соединить форматер с текстом *)
		WriteRuler(f);
		i := 0;
		ObxPhoneDB.LookupByIndex(i, name, number);
		WHILE name # "" DO
			f.WriteString(name);		(* первая строка *)
			f.WriteTab;					(* символ табуляции *)
			f.WriteString(number);	(* вторая строка *)
			f.WriteLn;						(* возврат каретки *)
			INC(i);
			ObxPhoneDB.LookupByIndex(i, name, number)
		END;
		v := TextViews.dir.New(t);	(* создать текстовой отображение для сгенерированного ранее текста  *)
		Views.OpenView(v)			(* открыть отображение в своем собственном окне *)
	END GenReport;

END ObxPDBRep2.
 
Листинг 5-8. Запись телефонной базы данных с применением форматера и линейки

    Выполнение ObxPDBRep2.GenReport приводит к появлению окна с улучшенным представлением телефонной базы данных. Выполнение Text->Show Marks приводит к отображению линейки:


Рисунок 5-9. Текстовый редактор содержащий таблицу базы данных

    Линейки являются отображениями. Сами по себе они ничего не значат. Как и остальные элементы управления линейки работают только внутри контейнера. В отличие от большинства других элементов управления, линейки являются примером элементов, которые работают правильно только внутри определенного контейнера, в данном случае, внутри текстовой модели. Можно скопировать линейку на форму или другой контейнер, но она останется пассивной и не даст никакого полезного эффекта. Если она внедрена в текстовую модель, которая отображается в визуальном представлении текста, то линейка может повлиять на способ форматирования следующего за ней текста. Например, линейка может определять позиции табуляции, отступы и тип выравнивания. 

    Параметры линейки действуют в определенной области. Она начинается от начала линейки и заканчивается перед следующей линейкой или доходит до конца текста, если других линеек дальше нет. Линейки определяют новый параграф, возможно со своим собственным набором атрибутов. Вставка символа начала параграфа (0EX) вместо линейки так же начинает новый параграф, но все его параметры наследуются от последней линейки. Символ параграфа - нормальный способ начать новый параграф, линейки необходимы только при необходимости изменения атрибутов параграфа. Следует отметить, что символ возврата каретки (0DX) не начинает новый параграф.

    Текстовое отображение содержит невидимую линейку по умолчанию, которая определяет форматирование текста до первой линейки или всего текста в случае, если линейка не была вставлена вовсе. Линейку по умолчанию, а также настройки по умолчанию можно изменить интерактивно командой меню Text->Make Default Ruler и Text->Make Default Attributes.

    У линейки есть много атрибутов, которые можно изменить. В отличие от параметров символов, которые можно применить к произвольному куску текста, атрибуты линейки применяются к линейке или ко всем параграфам в ее области действия.  
    Мы обсудим эти атрибуты, так что вы узнаете о функциональности линеек, когда вам это нужно. Однако мы не будем обсуждать линейки более подробно, т.к. вы можете изучить нужные детали, когда вам потребуется более продвинутые возможности линеек (см. документацию к модулю TextRulers).


    Стили

    Атрибуты линеек не хранятся в линейках отображения напрямую. Вместо этого они привязаны к отдельному объекту типа TextRulers.Attributes, который в свою очередь, содержится внутри объекта типа TextRulers.Style. Стиль - модель одного или нескольких отображений линеек. Если несколько отображений связаны с одной и той же моделью, то пользователь может изменить атрибуты всех линеек одновременно. Например, изменение левого отступа вступит в силу для всех линеек, основанных на данном стиле. Тем не менее, модель не может распределяться на несколько документов, что означает, что все границы линеек стилей должны быть внедрены в тот же документ.
    TextRulers.Attributes представляют собой коллекцию атрибутов линеек, подобно тому как TextModels.Attributes представляют собой коллекцию атрибутов символов. 
    
Attributes = POINTER TO EXTENSIBLE RECORD (Stores.Store)
		first-, left-, right-, lead-, asc-, dsc-, grid-: INTEGER;
		opts-: SET;
		tabs-: TextRulers.TabArray
	END;

Листинг 5-10. Упрощенное описание TextRules.Attributes

    В следующем параграфе будут описаны индивидуальные атрибуты. Для работы с этими атрибутами имеются вспомогательные процедуры в модуле TextRulers, которое облегчают установку линеек текста желаемым образом: создается новая линейка при помощи процедуры TextRulers.dir.New(NIL), а затем применяются вспомогательные процедуры для достижения требуемых атрибутов. Пример имеется в приведенной выше процедуре WriteRuler модуля ObxPdBRep2. Эти вспомогательные процедуры значительно облегчают установку текстовых линеек. Например, они прячут то, что атрибуты принадлежат не непосредственно линейкам, а специальному объекту атрибутов, который затем используется линейками. В терминологии шаблонов проектирования [GHJV94] обеспечение облегченного доступа к составным свойствам называется фасадом. Этот механизм простой, потому что фасад состоит из простых процедур, не требуется создавать и управлять какой-то дополнительный объект фасада. 


    Отступы табуляции 
    
    Поле tabs типа TextRulers.TabArray содержит информацию об отступах табуляции. Число отступов табуляции задано в tabs.len и не может быть больше TextRulers.maxTabs (текущее значение 32). Если пользователь вводит больше, чем maxTabs позиций отступов табуляции, или больше, чем заданое len, то лишние позиции будут заменены на пробелы фиксированной ширины. Фиксированная ширина вычисляется как умножение ширины пробела на целое число текущего шрифта, в общем, приблизительно 4 мм.

Tab = RECORD 
		stop: INTEGER;	(* отступ >= 0 *)
		type: SET	(* тип IN {TextRulers.centerTab, TextRulers.rightTab, TextRulers.barTab} *)
	END;

	TabArray = RECORD 
		len: INTEGER;	(* 0 <= len <= TextRulers.maxTabs *)
		tab: ARRAY TextRulers.maxTabs OF TextRulers.Tab
			(* tab[0 .. len-1] отсортированы в возрастающем порядке без дубликатов *)
	END

Листинг 5-11. Определение TextRulers.Tab и TabArray

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

tab type	tabs.tab[i].type
left adjusted	{}
right adjusted	{TextRulers.rightTab}
centered	{TextRulers.centerTab}

    В дополнение к вышеперечисленным типам есть возможность дополнительно определить будет ли отрисовываться вертикальная прографка (vertical bar). Прографка определяется включением в набор type значения TextRulers.barTab. Так как прографка занимает всю высоту строки, ее удобно применять для простых таблиц. 
    После вызова TextRulers.dir.New(NIL), линейка не содержит отступов табуляции.
    Следующие вспомогательные процедуры обеспечивают изменения линеек:

PROCEDURE AddTab (r: Ruler; x: INTEGER)
	PROCEDURE MakeRightTab (r: Ruler)
	PROCEDURE MakeCenterTab (r: Ruler)
	PROCEDURE MakeLineTab (r: Ruler)

    AddTab добавляет нормальный левовыровненный отступ табуляции. Он может быть изменен на правовыровненный или центральный вызовом MakeRight или MakeCenterTab. Вне зависимости от режима выравнивания, отступ табуляции может быть переведен в отступ с прографкой вызовом MakeLineTab.


    Поля 

    Текст представляет из себя набор с левым и правым полем. Эти поля можно определить как расстояние от левого края отображения текста. Левое поле должно быть меньше правого поля.

left-, right-: INTEGER	(* (left >= 0)  &  (right >= left) *)

    Иногда необходимо уведомить разметчика текста о необходимости игнорировать правое поле и использовать для отображения текста символ разрыва строки. Фактически, это поведение по умолчанию. Это означает, что при изменении размера отображения могут так же измениться разрывы строки. Даже если правое поле игнорируется, оно все равно еще полезно: текстовое отображение получает примерную оценку насколько широко следует его открыть.
    Разрывы строк при фиксированном правом поле необходимы в очень редких обстоятельствах, например, когда следующий параграф необходимо уместить рядом с предыдущим. Если необходимо, чтобы строки разрывались только по правому полю, то необходимо включить TextRulers.rightFixed в набор opts (смотри ниже).
    Следует отметить, что команда Tools->Document Size... позволяет гибко назначить ширину (и высоту) первоначального отображения документа, так же называемого корневым отображением (root view). Ширина может быть назначена фиксированным размером, что полезно для приложений с разметкой страниц.
    Тем не менее, чаще всего ширина определяется настройками страницы, которая наследует ширину и высоту отображения для текущей выбранной страницы, урезанные на пользовательские поля. Это отвечает требованиям таких документов как: письма, программный код и т.п. 
    Третьей возможностью может быть ограничение ширины корневого отображения рамками размеров окна (как упоминалось выше, когда отображение использует размер правого поля из линейки как подсказку, вне зависимости от того фиксирована ли правое поле или нет). При любом изменении размеров окна, корневое отображение изменяет размер соответствующим образом, чтобы в окне уместилось столько информации, сколько возможно. Это удобно для типичного окна встроенной документации, в котором информация должна занимать столько места на экране, сколько для этого позволит пользователь, текст должен адаптироваться по ширине вне зависимости от изменения размеров окна. Примером подобного стиля является форматирование по умолчанию для Веб-страницы.
    Следует отметить, что эта особенность не зависит от типа корневого отображения. Она работает подобным образом для всех отображений, способных менять размеры, таких как текстовое отображение, форма и большинство других редакторов. 
    При создании при помощи TextRulers.dir.New(NIL) линейка получает левое поле равное 0 и правое нефиксированное поле больше 0 в зависимости от специфики реализации.  
    Следующие вспомогательные процедуры обеспечивают изменение линейки:

PROCEDURE SetLeft (r: Ruler; x: INTEGER)
	PROCEDURE SetRight (r: Ruler; x: INTEGER)
	PROCEDURE SetFixedRight (r: Ruler; x: INTEGER)

    SetLeft и SetRight устанавливают левое и правое поле. SetFixedRight выставляет правое поле и делает его фиксированным.


    Отступ первой строки 

    Линейки задают отступ первой строки, то есть насколько первая строка после линейки будет сдвинута от левой границы текстового отображения. Следует отметить, что можно задать отступ первой строки меньше, чем левое поле (first < left). Это возможно только в случае, если текстовое отображение выводит текст при определенном значении левого поля.

first-: INTEGER;	(* first >= 0 *)

    Отступ первой строки применяется для всех первый строк каждого параграфа в пределах действия линейки.
    При создании линейки с помощью TextRulers.dir.New(NIL) линейка не имеет отступа для первой строки (first = 0). Следующая вспомогательная процедура обеспечивает изменение для линейки:

PROCEDURE SetFirst (r: Ruler; x: INTEGER)

    Над- и подстрочные элементы

    При при отрисовке символа его изображение зависит от атрибутов шрифта, таких как гарнитура  (Helvetica или Times?), стиль (обычное или наклонное?), начертание (нормальное или жирное) и размер. При отрисовке строки целиком все символы размещаются на обычной базовой линии. Надстрочный элемент - максимальный выступ символа над базовой линией. Некоторые символы имеют выступ под базовую линию, например "у", "р". Подстрочный элемент - максимальный выступ любого символа под базовую линию (см. рис. 5-12). Разметчик текста должен вычислять расстояние между базовыми линиями для предотвращения перекрытия надстрочных и подстрочных элементов соседних строк во избежание наложения символов. Во избежание вырожденных случаев, таких как пустая строка или строка с экстремальными размерами шрифтов, линейки могут задать минимальные значения для надстрочных (asc) и подстрочных (des) элементов.

asc-, dsc-: INTEGER;	(* (asc >= 0)  &  (dsc >= 0) *)


Рисунок 5-12. Над-, подстрочные элементы и базовая линия

    При создании линейки с помощью TextRulers.dir.New(NIL) для надстрочных и подстрочных элементов используются значения текущего шрифта по умолчанию (Fonts.dir.Default()) в качестве начальных значений.
    Следующие вспомогательные процедуры обеспечивают изменение линейки:

PROCEDURE SetAsc (r: Ruler; h: INTEGER)
	PROCEDURE SetDsc (r: Ruler; h: INTEGER)


    Отбивка параграфа 

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

lead-: INTEGER;	(* lead >= 0 *)

    При создании линейки с помощью TextRulers.dir.New(NIL) отбивка устанавливается в 0.
    Следующая вспомогательная процедура обеспечивает изменение линейки:

PROCEDURE SetLead (r: Ruler; h: INTEGER)

    Сетка линеек 

    Если значение grid равно нулю, то линии располагаются настолько близко, насколько это возможно, чтобы не оставалось свободного месте между надстрочными и подстрочными элементами соседних строк. Разные строки могут иметь разные надстрочные и подстрочные элементы, в зависимости от текста и шрифта содержимого. По этой причине расстояние между базовыми линиями необходимо делать нерегулярным. Из соображений типографики, нерегулярное расстояние между строками часто нежелательно.
    Для закрепления регулярного расположения сетки линеек возможно установка сетки линеек путем присвоения значению grid значения больше нуля. Это значение определяет вертикальную сетку, согласно которой принудительно выставляются базовые линии. В случае, если строка слишком высокая для размещения на следующем участке сетки, то она располагается на одну или несколько позиций сетки ниже (см рисунок 5-14).

grid-: INTEGER;	(* grid >= 0 *)


Рисунок 5-13. Сетка строк

    В момент создания с помощью TextRulers.dir.New(NIL) линейка имеет значение линий сетки равное 1. Следующая вспомогательная процедура обеспечивает изменение линейки:

PROCEDURE SetGrid (r: Ruler; h: INTEGER)

    Режимы выравнивания 

    Для длинных участков текста бывает необходимо разрывать строки между полями отображения. В зависимости от настроек, поведение текста может быть различным. Текст может быть выровнен по левому краю (левая выключка), по правому краю (правая выключка), центрирован или выровнен одновременно по левому и правому полям (полная выключка). Эти четыре режима выравнивания контролируются двумя флагами: TextRulers.leftAdjust и TextRulers.rightAdjust

	leftAdjust	rightAdjust	adjustment mode
	FALSE	FALSE	centered
	FALSE	TRUE	right adjusted
	TRUE	FALSE	left adjusted (default)
	TRUE	TRUE	fully adjusted

Таблица 5-14. Режимы выравнивания

    режимы выравнивания могут быть включены или выключены исчерпывающим набором, оформленным в виде множества:

opts-: SET	(* opts это подмножество {leftAdjust, rightAdjust, pageBreak, rightFixed, noBreakInside, parJoin; другое множество элементов зарезервировано *)

    В момент создания с помощью TextRulers.dir.New(NIL) значение набора выравнивания установлено в {leftAdjust}.
    Следующие вспомогательные процедуры обеспечивают модификации линейки:

PROCEDURE SetLeftFlush (r: Ruler)
	PROCEDURE SetCentered (r: Ruler)
	PROCEDURE SetRightFlush (r: Ruler)
	PROCEDURE SetJustified (r: Ruler)

    Разрывы страницы 

    Линейки могут выполнять принудительный переход на новую страницу путем включения TextRulers.pageBreak в набор opts. Элемент TextRulers.noBreakInside действует на всей области видимости или на параграф. Это означает принудительный перенос начала параграфа на новую страницу, кроме случая, когда страница начинается сразу после конца параграфа.
    Элемент TextRulers.parJoin подобен noBreakInside за исключением дополнительной проверки, что как минимум первая строка следующего параграфа будет на той же странице.
    В момент создания линейки с помощью TextRulers.dir.New(NIL) в ней не содержится перечисленных выше атрибутов. Следующие вспомогательные процедуры обеспечивают модификации линеек:

PROCEDURE SetPageBreak (r: Ruler)
	PROCEDURE SetNoBreakInside (r: Ruler)
	PROCEDURE SetParJoin (r: Ruler)

    На этом описание атрибутов линеек завершено. Атрибуты линеек применяются ко всем линейкам или параметрам в пределах видимости. Атрибуты символов напротив применяются только для конкретного участка текста. Текстовая модель Черного Ящика поддерживает все атрибуты текста плюс цвет и вертикальное смещение.
    Линейки - отображения, которые могут быть вставлены в текст, поскольку текст является контейнером. Линейки по определению знают, что такое текстовая система, но текстовому отображению необходимо знать о параметрах параграфов, выставляемых с помощью линеек. Тем не менее, несколько других отображений, которые ничего не знают о текстовой системе, могут быть вставлены в текст. В частности интересные эффекты могут быть достигнуты, если отображения знают о тексте. Закладки (Fold views) и ссылки (link views) - примеры подобного текстоориентированного отображения.
      Следующий пример показывает каким образом можно создать закладку. Закладка - стандартный элемент системы BlackBox Component Builder; она реализована в модуле StdFolds с применением только BlackBox и текстовой подсистемы. Закладки идут парами: левая закладка и правая закладка. Левая закладка содержит скрытый текст. Когда пользователь щелкает на одном из отображений закладки, текст между значками заменяется на скрытый текст. Обычно закладки применяются для подмены большого участка текста его короткой характеристикой, например, глава книги может быть спрятана, если виден только заголовок главы. Это один из способов увеличения абстракции документа путем сокрытия деталей. По этой причине одно состояние отображения закладки называется свернытым, другое называется развернутым состоянием. Нет технических причин для того, чтобы скрытый текст был короче, чем видимый; отображение наименования лишь пример наиболее типичного использования закладок.

Рисунок 5-15. Отображение закладки в свернутом и развернутом состояниях


Рисунок 5-16. Структура текстовой закладки

MODULE ObxPDBRep3;

	IMPORT Views, TextModels, TextMappers, TextViews, StdFolds, ObxPhoneDB;					
	PROCEDUREWriteOpenFold(VARf:TextMappers.Formatter;
															INshortForm:ARRAYOFCHAR);
		VAR fold: StdFolds.Fold; t: TextModels.Model;
	BEGIN
		t := TextModels.dir.NewFromString(shortForm);	(* конвертирование строки в текстовую модель *)
		fold := StdFolds.dir.New(StdFolds.expanded, "", t);
		f.WriteView(fold)
	END WriteOpenFold;

	PROCEDUREWriteCloseFold(VAR f: TextMappers.Formatter);
		VAR fold: StdFolds.Fold; len: INTEGER;
	BEGIN
		fold := StdFolds.dir.New(StdFolds.expanded, "", NIL);
		f.WriteView(fold);
		fold.Flip;	(* замена длинного текста, теперь между двумя отображениями закладками, с коротким текстом *)
		len := f.rider.Base().Length();	(* определить, что носитель имеет новую длину *)
		f.SetPos(len)	(* установить позицию форматера в конец текста *)
	END WriteCloseFold;

	PROCEDURE GenReport*;
		VAR t: TextModels.Model; f: TextMappers.Formatter; v: TextViews.View;
			i: INTEGER; name, number: ObxPhoneDB.String;
	BEGIN
		t := TextModels.dir.New();	(* создать пустой текстовый носитель *)
		f.ConnectTo(t);					(* соединить форматер с текстом *)
		i := 0;
		ObxPhoneDB.LookupByIndex(i, name, number);
		WHILE name # "" DO
			WriteOpenFold(f,name$);	(* записать левую закладку-отображение в текст, с ее именем в качестве короткого текста *)
			(* теперь записать длинный текст *)
			f.WriteString(name);		(* первая строка *)
			f.WriteTab;					(* символ табуляции *)
			f.WriteString(number);	(* вторая строка *)
			WriteCloseFold(f);			(* записать закрывающую закладку, заменить длинный и короткий текст *)
			f.WriteLn;						(* возврат каретки *)
			INC(i);
			ObxPhoneDB.LookupByIndex(i, name, number)
		END;
		v := TextViews.dir.New(t);	(* создать текстовое отображение для текста, сгенерированного выше *)
		Views.OpenView(v)			(* открыть отображение в своем собственном окне*)
	END GenReport;

END ObxPDBRep3.


Листинг 5-17. Запись телефонной базы данных с применением закладок

    Процедура WriteCloseFold обладает двумя интересными особенностями. Во-первых, закладка записывается в развернутом состоянии. На это есть причина: часто развернутая форма более сложна, чем свернутая, поэтому более удобно использовать WriteOpenFold (следует отметить, удобную процедуру TextModels.dir.NewFromString). Если свернутая форма более сложная, более удобно воспользоваться уже имеющимся форматером. После записи левой части, развернутого текста и правой части закладки, текст может быть свернут процедурой StdFolds.Flip.
    Это приводит ко второму интересному моменту: свертывание текста приводит к изменению текстовой модели. Это приводит к устареванию всех курьеров, и, как результат, всех форматеров, базирующихся на тексте. Например, форматер f всегда должен находится в конце созданного текста, но когда текст свернули, его принадлежность к концу текста не соответствует действительности. По этой причине необходимо заново выставить его позицию.
    Это является общим поведением при работе с переменными носителями: при любом изменении статуса носителя (вне зависимости от того, кто это сделал) необходимо обновить некоторые статусы состояний курьеров. В данном примере изменяется длина текста, что приводит к необходимости обновления позиции форматера.
    В последнем примере из текущей серии будет показано как создать текст с использованием ссылочных отображений. Ссылки - стандартный элемент BlackBox Component Builder; они реализованы в модуле StdLinks только на основе базовой подсистемы и подсистемы Text. Подобно закладкам, ссылки используются парами. Их реализация применяет специальный интерфейс текстовой подсистемы (TextControllers.FilterPollCursorMsg, TextControllers.FilterTrackMsg), который заменяет стандартный указатель мыши на изображение пальца (hand cursor) при наведении его на текст между отображениями ссылки. Такое поведения известно по типичным гипертекстовым системам, подобным броузерам, когда перемещение указателя мыши по тексту символизирует гиперссылку. Прямое назначение ссылки следует из его названия. Тем не менее имеется более общее назначение, чем простая разметка. Гиперссылка содержит пассивную связь с другим документом или с другим местом этого же документа. Например, ссылка на документ может быть сохранена в виде строки: "http://www.oberon.ch" или "BlackBox/System/Rsrc/About" 
    В отличие от команд Component Pascal, ссылка дополнительно может иметь один или два строковых параметра. Команда выполняется после щелчка мыши пользователя при нахождении ее указателя на тексте между двумя отображениями ссылки.
    Команда сохранена в левой части пары отображений ссылки. Типичная команда:

DevDebug.ShowLoadedModules

	StdCmds.OpenDoc('System/Rsrc/Menus')

	StdCmds.OpenAux('System/Rsrc/About', 'About BlackBox')

    В нашем примере необходимо создать отображения ссылки со следующей примерной командой:

ObxPDBRep4.Log('Daffy Duck310-555-1212')

    Замечание: необходимо проконсультироваться по модулю StdInterpret, так как могут иметься некоторые ограничения по поддержке возможного списка параметров или команд.
    Процедура Log в приведенном примере просто записывает строку, переданную как параметр, в рабочий журнал. В более реалистичном примере параметр может применяться для набора соответствующего номера.

MODULE ObxPDBRep4;

	IMPORT Views, TextModels, TextMappers, TextViews, StdLinks, StdLog, ObxPhoneDB;

	CONST cmdStart = "ObxPDBRep4.Log('"; cmdEnd = "')";

	PROCEDURE GenReport*;
		VAR t: TextModels.Model; f: TextMappers.Formatter; v: TextViews.View;
			i: INTEGER; name, number: ObxPhoneDB.String; link:StdLinks.Link;
	BEGIN
		t := TextModels.dir.New();	(* создать пустого текстового носителя *)
		f.ConnectTo(t);					(* соединить форматер с текстом*)
		i := 0;
		ObxPhoneDB.LookupByIndex(i, name, number);
		WHILE name # "" DO
			link:=StdLinks.dir.NewLink(cmdStart+name+""+number+cmdEnd);
			f.WriteView(link);
			f.WriteString(name);		(* строка показывается между двумя ссылочными отображениями *)
			link:=StdLinks.dir.NewLink("");
			f.WriteView(link);
			f.WriteLn;						(* возврат каретки *)
			INC(i);
			ObxPhoneDB.LookupByIndex(i, name, number)
		END;
		v := TextViews.dir.New(t);	(* создать текстовое отображение для текста, сгенерированного выше *)
		Views.OpenView(v)			(*открыть отображение в своем собственном окне *)
	END GenReport;

	PROCEDURELog*(param:ARRAYOFCHAR);
	BEGIN
		StdLog.String(param); StdLog.Ln
	END Log;

END ObxPDBRep4.

Листинг 5-18. Запись телефонной БД с применением ссылок. 

    Следует отметить, что команды являются вызовами обычной процедуры; как и все вызовы процедур за пределами модуля, возможен только вызов процедуры объявленной как экспортируемая. Процедура Log из представленного модуля должна быть экспортирована, в противном случае программа не будет работать. 
    Другим интересным свойством отображений ссылки является разный способ отрисовки в зависимости от того заказал пользователь отображать или прятать метки для текстового отображения. При наличии нескольких текстовых отображений одной модели пользователь может независимо переключать отметки для отображения или для скрытия, а так же деактивировать мягкие переносы линеек или отображений ссылки. Текстовая подсистема обеспечивает интерфейс, который позволяет отображениям различать два состояния (TextSetters.Pref). В частности, отображение может принять решение для своего сокрытия, когда не отображаются метки, выставляя собственную ширину в ноль. Это то, что и делает отображение ссылки: когда текстовые отметки выключены, они невидимы, а виден только текст между ними. Обычно текст отображается подчеркнутым и синим цветом, для обозначения ссылки. Линейки, напротив, всегда занимают все пространство между левым и правым полем параграфа, но всегда выставляют свою ширину в ноль при выключенном отображении метки. О взаимодействии контейнеров и внедренных отображениях для достижения подобных эффектов будет рассказано дальше в части III этого учебника.
    В приведенном выше примере показано, что отображения ссылок могут вести себя разными путями при щелчке пользователя между ними. В общем, это мощное средство, но его нужно использовать с осторожностью. Команды в документе представляют собой вид "безусловного импорта" взаимосвязей между документами и командными модулями и поэтому порождают зависимости. Это не является фундаментальным отличием от случая зависимостей между внедренным в документ отображениям, они так же функционируют правильно только при доступности задействованного компонента(ов). Подобно всем зависимостям, как в случае ссылки, внедрения кнопки на элемент управления, внедрения редактора в документ и других, их необходимо отслеживать и обновлять по мере необходимости. 
    

    5.2 Шаблон Модель-Отображение-Контроллер в применении к текстам 
    
    Следующий основной пример показывает, как можно прочитать имеющийся текст. Предполагается, что обрабатываемый текст видимый, в самом верхнем окне. По доступу к тестам, сохраненным в файле обращайтесь к встроенной документации Obx на примерах ObxOpen0, ObxOpen1 и ObxAscii. Перед началом рассказа о специфике чтения текстов необходимо рассказать каким образом можно получить доступ к содержимому самого верхнего окна с помощью команд Component Pascal.
    По существу это делается вызовом Controllers.FocusView(). Функция возвращает NIL если нет открытого документа. В других случаях возвращается содержимое самого верхнего окна, то есть отображение.
    Ранее было сказано, что отображения, основанные на общем типе Views.View есть центральная абстракция BlackBox для интерактивных компонентов, таких как редакторы и элементы управления. Все вращается вокруг отображений. В простых случаях, таких как элементы управления, реализация отображений представляет собой расширения Views.View и все. В более сложных случаях данные отображаются пользователю в отображениях, разделенных на несколько моделей. Это подробно обсуждалось в главе 2.
    Так как реализация контейнеров является одной из самых сложных задач программирования с применением BlackBox, среда разработки обеспечивает помощь в методике построения контейнеров. Абстрактный проект контейнера приведен в модуле Containers. В модуле определены расширения типов Views.View, Models.Model и Controllers.Controller, которые представляют собой миниоболочку для построения контейнеров.
    Обычный контейнер состоит как минимум из четырех модулей: один для модели, один для отображения, один для контроллеров (controller) и один для стандартных команд. Например, текстовая подсистема состоит из модулей: TextModels, TextViews, TextControllers, TextCmds и дополнительные модули: TextMappers, TextRulers и TextSetters. Подсистема форм состоит из: FormModels, FormViews, FormControllers и FormCmds.


Рисунок 5-19. Варианты декомпозиции основных контейнеров

    Сейчас не рассматриваются эти типы детально; но необходимо знать, что объекты контейнеров не только разделены на модели или несколько отображений, но так же и то, что отображения разделяются в дальнейшем на отображения и так называемые контроллеры. Со временем будет уделено внимание управлению выделением и положению фокуса ввода, так как основная функциональность сосредоточена в контроллере. Поэтому сейчас заостряется внимание на контроллерах, потому что последующие примеры оперируют с текущим выделением.
    Если имеется контроллер, то извлечь его отображение можно вызовом процедуры ThisView. Другие процедуры позволяют получить или выставить текущее выделение.
    Может быть несколько отображений, показываемых в одной модели (отношение один-ко-многим), но всегда только один контроллер на одно отображение (отношение один-к-одному). (Именно поэтому отображения произведены от Containers.View, другие отображения могут содержать, а могут и не содержать отдельные контроллеры). Можно получить отображение для контроллера с помощью процедуры ThisView, и можно получить контроллер для отображения с помощью процедуры ThisController.


Рисунок 5-20. Строение абстракции Модель-Отображение-Контроллер. (Model-View-Controller separation)

    Для более специфических абстракций контейнеров обычно предоставляется подходящая функция Focus, которая извлекает контроллер для текущего отображения, обладающего фокусом, если и когда оно имеет подходящий тип.
    Теперь достаточно знаний о сценариях для контейнеров для доступа к отображениям, моделям и контроллерам, попадающим в фокус.

Примем следующие объявления:
		VAR v: Views.View; m: Models.Model; c: Controllers.Controller;

	Простое отображение:
		v := Controllers.FocusView();

	Отображение по модели:
		v := Controllers.FocusView();
		IF v # NIL THEN m := v.ThisModel() ELSE m := NIL END;

    Расширение Containers.View:
		v := Controllers.FocusView();
		IF v # NIL THEN m := v.ThisModel() ELSE m := NIL END;
		IF (v # NIL) & (v IS Containers.View) THEN
			c := v(Containers.View).ThisController()
		ELSE
			c := NIL
		END
Листинг 5-21. Доступ к отображению, модели и контроллеру, обладающими фокусом

    Типичная абстракция контейнера, подобная текстовой подсистеме или подсистеме форм, использует указанный выше механизм для обеспечения более удобных функций доступа, основанных на особенностях типа контейнера. Например, модуль TextControllers экспортирует функцию Focus, которая возвращает фокус контроллера отображения когда и если отображение с фокусом является текстовым отображением. В других случаях возвращается NIL. Этот подход оправдан, так как если имеется контроллер контейнера, необходим легкий доступ к его отображению, модели, информации о выделении или положении фокуса ввода и т.п. Типичный шаблон кода является подмножеством нижеприведенного, в зависимости от того какая часть контролера необходима:

PROCEDURE SomeCommand*;
		VAR c: TextControllers.Controller;
			v: TextViews.View; t: TextModels.Model; from, to, pos: INTEGER;
			...
	BEGIN
		c := TextControllers.Focus();
		IF c # NIL THEN
			...
			v := c.ThisView();	(*если нужно текстовое отображение... *)
			...
			t := v.ThisModel();	(*или нужна текстовая модель... *)
			...
			IF c.HasSelection() THEN	(* или нужен диапазон выделения... *)
				c.GetSelection(from, to);
				...
			END;
			...
			IF c.HasCaret() THEN	(* или нужна позиция знака вставки... *)
				pos := c.CaretPos();
				...
			END;
			...
		ELSE	(* не открывается окно документа, или фокус имеет неверный тип *)
			(* не обрабатывать ошибку необходимо если эта команда охраняется подходящей командой охранника *)
		END
	END SomeCommand;
     
Листинг 5-22. Шаблон кода для TextControllers.Controller

    Команда меню может использовать охранника TextCmds.FocusGuard для уверенности, что команда должна быть доступна только при наличии фокуса на текстовом отображении. TextCmds.SelectionGuard дополнительно проверяет наличие (непустого) выделения текста.


    5.3 Чтение текста

    Сейчас рассмотрим каким образом можно получить доступ к контроллеру отображения, имеющего фокус, напишем команду для чтения текста из отображения, имеющего фокус. Команда подсчитает количество символов из набора Latin-1 (1 байт), Unicode (над Latine-1, 2 байта) и количество внедренных отображений.

MODULE ObxCount0;

	IMPORT TextModels, TextControllers, StdLog;

	PROCEDURE Do*;
	(** используйте TextCmds.SelectionGuard в качестве охранника для этой команды **)
		VAR c: TextControllers.Controller; from, to, schars, chars, views: INTEGER;
			rd: TextModels.Reader;
	BEGIN
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			c.GetSelection(from, to);	(* взять диапазон выделения; от < до *)
			rd := c.text.NewReader(NIL);	(* создать нового считывателя для текстовой модели *)
			rd.SetPos(from);	(* установить считывателя на начало выделения *)
			rd.Read;				(* считать первый элемент текстового выделения *)
			schars := 0; chars := 0; views := 0;	(* переменные для подсчета *)
			WHILE rd.Pos() # to DO	(* прочесть все элементы выделения *)
				IF rd.view # NIL THEN	(* элемент - это отображение *)
					INC(views)
				ELSIF rd.char < 100X THEN	(* элемент - это символ Latin-1 *)
					INC(schars)
				ELSE	(* элемент - это символ Unicode *)
					INC(chars)
				END;
				rd.Read		(* считать следующий элемент *)
			END;
			StdLog.String("Latin-1 characters: "); StdLog.Int(schars); StdLog.Ln;
			StdLog.String("Unicode characters: "); StdLog.Int(chars); StdLog.Ln;
			StdLog.String("Views: "); StdLog.Int(views); StdLog.Ln;
			StdLog.Ln
		END
	END Do;

END ObxCount0.

Листинг 5-23. Посчет числа символов Latin-1, Unicode и отображений в тексте    

    В приведенном выше примере используется TextModels.Reader, который является курьером для операции чтения. Каждый считыватель сохраняет свою текущую позицию в его базовом тексте; может быть несколько независимых считывателей для одного и того же текста одновременно. Считыватель текста имеет следующее определение:

TYPE
	Reader = POINTER TO ABSTRACT RECORD 
		eot: BOOLEAN;
		attr: TextModels.Attributes;
		char: CHAR;
		view: Views.View;
		w, h: INTEGER;
		(rd: Reader) Base (): TextModels.Model, NEW, ABSTRACT;
		(rd: Reader) Pos (): INTEGER, NEW, ABSTRACT;
		(rd: Reader) SetPos (pos: INTEGER), NEW, ABSTRACT;
		(rd: Reader) Read, NEW, ABSTRACT;
		(rd: Reader) ReadChar (OUT ch: CHAR), NEW, ABSTRACT;
		(rd: Reader) ReadView (OUT v: Views.View), NEW, ABSTRACT;
		(rd: Reader) ReadRun (OUT attr: TextModels.Attributes), NEW, ABSTRACT;
		(rd: Reader) ReadPrev, NEW, ABSTRACT;
		(rd: Reader) ReadPrevChar (OUT ch: CHAR), NEW, ABSTRACT;
		(rd: Reader) ReadPrevView (OUT v: Views.View), NEW, ABSTRACT;
		(rd: Reader) ReadPrevRun (OUT attr: TextModels.Attributes), NEW, ABSTRACT
	END;

Листинг 5-24. Определение TextModels.Reader

    Для данной текстовой модели text может быть создан считыватель вызовом rd := text.NewReader(NIL). Вновь размещенный считыватель позиционируется на начало текста, то есть на позицию rd.Pos() = 0. Позиция может быть изменена при необходимости с помощью вызова rd.SetPos(pos), где pos находится в диапазоне 0..text.Length(). Текстовая модель для считывателя может быть получена вызовом процедуры Base.
    Чтение следующего элемента текста осуществляется процедурой считывателя Read, которая увеличивает текущую позицию считывателя, пока не достигнут конец текста. В этом случае устанавливается поле считывателя eot. После вызова Read прочитанный символ доступен из поля char. Если значение символа меньше, чем 100X, символ попадает в диапазон Latin-1 (1-байтовый символ), в противном случае это - 2-байтовый символ Unicode. Для удобства поддерживается вспомогательная процедура ReаdChar, которая выполняет Read и возвращает содержимое char.
    Что случится, если будет прочитано отображение? В большинстве случаев поле char выставится в зарезервированное значение TextModels.viewcode (2X). Тем не менее, в этом нет необходимости. Текстоориентированные отображения могут предоставлять несколько кодов символов с применением специального сообщения предпочтений (TextModels.Pref). При необходимости определить факт прочтения отображения, необходимо проверить поле view. Оно возвращает только что прочитанное отображение или NIL, если прочитано не отображение. 
    Предсказуемо наличие реализации для текстовой модели оптимизированного доступа к отображениям. Для этой цели служит процедура ReadView, которая читает следующее отображение после текущей позиции, при необходимости пропуская все элементы, не являющиеся отображениями.
    После прочтения элемента (символа или отображения) его текстовые атрибуты возвращаются в поле читателя attr. Текстовые атрибуты состоят из атрибутов шрифта, цвета и вертикального смещения и описаны выше. По соображениям эффективности и удобства поддерживается вспомогательная процедура ReadRun, которая перемещает позицию считывателя до смены атрибутов в сравнении с текущей позицией. Фрагмент текста, имеющий одинаковые атрибуты может быть отрисован на экране одной процедурой (Port.Frame.DrawString), что значительно быстрее отображения символов одного за другим. Обычно текст читается вперед от меньшей позиции к большей. Процедуры считывателя ReadPrev, ReadPrevView и ReadPrevRun симметричны своим аналогам Read, ReadView и ReadRun, но чтение осуществляется в обратном порядке (назад). В частности, ReadPrevView применяется для текстовых отображений, так же называемых установщиками текста  для поиска наиболее близкого отображения линейки, чтобы определить какие параметры текста необходимо выставить (этот поиск осуществляется во вспомогательной процедуре TextRulers.GetValidRuler).
    При вызове одной из процедур чтения (вне зависимости от того читают ли они вперед или назад), устанавливается значение поля считывателя eot. Если элемент может быть прочтен, eot выставляется в FALSE. Если ни один из элементов не может быть прочтен (чтение за концом или началом файла) eot выставляется в TRUE.
    Обычно применяется следующий шаблон при работе с текстовыми считывателями. Условие может быть чем-то вроде rd.Pos() # end или ~rd.eot.

VAR rd: TextModels.Reader; start: INTEGER;
	BEGIN
		... define start ...
		rd := text.NewReader(NIL);
		rd.SetPos(start);	(* необходимо только если старт # 0 *)
		rd.Read;
		WHILE condition DO
			... олределить текстовй элемент обозначенный при помощи rd ... 
			rd.Read
		END;

Листинг 5-25. Шаблон кода для TextModels.Reader

    Следующий пример использует сканер текста. Сканеры читают текстовые отображатели; как и форматеры они определены в модуле TextMappers. Текстовые сканеры подобны текстовым считывателям за исключением того, что они работают с образом целиком, в отличие от (текстовых) символов. Строки и числа являются примерами символов Component Pascal, которые поддерживаются текстовыми сканерами.
    Если сравнить определение TextMappers.Scanner с определением TextModels.Reader, можно заменить, что сканер так же может вернуть символ (поле char) и отображение вместе с размерами (поля view, w и h), что сканер обладает позицией (Pos, SetPos), но сканер не может читать назад. Scan соответствует процедуре считывателя Read. Она пропускает пустые места (пробелы, возврат каретки), пока не будет достигнут конец текста, что указывается выставлением поля type в TextMappers.eot или пока не будет прочитан символ. Тип прочитанного символа (образа) возвращается в type. Он может быть одним из констант модуля TextMappers:
char, string, int, real, bool, set, view, tab, line, para, eot, invalid.
    По умолчанию отображения и управляющие символы распознаются как пустое пространство, поэтому игнорируются. Применение дополнительного набора (opts, SetOpts) позволяет распознавать их как действующие символы. Элемент набора TextMappers.returnViews указывает, что отображения распознаются, TextMappers.returnCtrlChars указывают, что три управляющих символа: табуляция, возврат каретки и параграф так же распознаются. Если символ начинается как строка Component Pascal или идентификатор, сканер пытается прочитать строку. В случае успеха type выставляется в string; которая так же может быть и короткой строкой, то есть содержать только символы Latin-1. Опция TextMappers.returnQualIdents позволяет сканеру различать целиком определенные идентификаторы как один символ, то есть строку ThisMod.ThatObj. Длина строки возвращается в len. 
    Если возможно преобразовать символ в целое или дробное число, type выставляется в соответствующий тип, а так же выставляется поле int или real. Целые числа распознаются в различных системах исчисления, распознанная система исчисления возвращается в base (10 для десятичных чисел)
    Специальные символы !@#$ распознаются как тип TextMappers.char, которые могут быть так же короткими символами, то есть символами Latin-1.
    По умолчанию логические значения и множества не интерпретируются за исключением случая, когда выставлены параметры TextMappers.interpretBool или TextMappers.interpretSets. Для множеств применяется обычный синтаксис Component Pascal, логические значения представлены как $TRUE и $FALSE.
    Поле сканера start содержит позицию первого символа, который будет сейчас считан. Поля lines и paras содержат количество символов возврата каретки и параграфа с последнего обращения к процедуре SetPos (см. ниже). Процедура сканера Skip является вспомогательной процедурой для достижений сканером первого непустого символа (как указано в множестве opts) и чтения его. Этот символ возвращается в параметре ch в процедуре Skip's.
    Так как может быть несколько специализированных текстовых отображателей, в дополнение к ним поддерживается TextMappers, текстовая модель не может создать объект текстового отображателя, подобно тому как она создает текстовые считыватели и записыватели. Вместо этого сканеры (так же как и форматтеры) в явном виде соединяются с текстом путем вызова процедуры ConnectTo. Она может быть вызвана повторно для соединения того же сканера с другой моделью.

Scanner = RECORD
		rider-: TextModels.Reader;
		opts-: SET;
		type: INTEGER;
		start, lines, paras: INTEGER;
		char: CHAR;
		int, base: INTEGER;
		real: REAL;
		bool: BOOLEAN;
		set: SET;
		len: INTEGER;
		string: TextMappers.String;
		view: Views.View;
		w, h: INTEGER;
		(VAR s: Scanner) ConnectTo (text: TextModels.Model), NEW;
		(VAR s: Scanner) Pos (): INTEGER, NEW;
		(VAR s: Scanner) SetPos (pos: INTEGER), NEW;
		(VAR s: Scanner) SetOpts (opts: SET), NEW;
		(VAR s: Scanner) Scan, NEW;
		(VAR s: Scanner) Skip (OUT ch: CHAR), NEW
	END;

Листинг 5-26. Определение TextModels.Scanner

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

MODULE ObxCount1;

	IMPORT TextModels, TextMappers, TextControllers, StdLog;

	PROCEDURE Do*;
	(** use TextCmds.SelectionGuard as guard for this command **)
		VAR c: TextControllers.Controller; from, to, ints, reals, strings: INTEGER;
			s: TextMappers.Scanner;
	BEGIN
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			c.GetSelection(from, to);	(* получить диапазон выделения; от < до *)
			s.ConnectTo(c.text);	(* соединить сканер с этой текстовой моделью *)
			s.SetPos(from);	(* установить считывателя на начало *)
			s.Scan;				(* прочитать первый символ *)
			ints := 0; reals := 0; strings := 0;	(* переменные для подсчета *)
			WHILE s.Pos() < to DO	(* прочесть все символы с начала выделения *)
				IF s.type = TextMappers.int THEN	(* символ - это целое число *)
					INC(ints)
				ELSIF s.type = TextMappers.real THEN	(* символ - это вещественное число *)
					INC(reals)
				ELSIF s.type = TextMappers.string THEN	(* символ - это строка/идентификатор *)
					INC(strings)
				END;
				s.Scan		(* прочитать следующий символ выделения *)
			END;
			StdLog.String("Integers: "); StdLog.Int(ints); StdLog.Ln;
			StdLog.String("Reals: "); StdLog.Int(reals); StdLog.Ln;
			StdLog.String("Strings: "); StdLog.Int(strings); StdLog.Ln;
			StdLog.Ln
		END
	END Do;

END ObxCount1.

Листинг 5-27. Подсчет целочисленных, дробных чисел и строк в тексте

    Наиболее типичный шаблон кода для применения сканера выглядит подобно шаблону для считывателя. Условие может содержать что-то вроде: s.pos() < end или s.type # TextMappers.eot.

VAR s: TextMappers.Scanner; start: INTEGER;
	BEGIN
		... define start ...
		s.ConnectTo(text);
		s.SetPos(start);	(* необходимо, только если start # 0 *)
		s.Scan;
		WHILE condition DO
			IF s.type = TextMappers.int THEN
				... consume s.int ...
			ELSIF s.type = TextMappers.real THEN
				... consume s.real ...
			ELSIF ...
				...
			END;
			s.Scan
		END;

Листинг 5-28. Шаблон кода для TextMappers.Scanner


    5.4 Изменение текста

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

MODULE ObxLookup0;

	IMPORT TextModels, TextMappers, TextControllers, ObxPhoneDB;

	PROCEDURE Do*;
	(** используйте TextCmds.SelectionGuard в качестве охранника команды **)
		VAR c: TextControllers.Controller; buf: TextModels.Model; from, to: INTEGER;
			s: TextMappers.Scanner; f: TextMappers.Formatter; number: ObxPhoneDB.String;
	BEGIN
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			c.GetSelection(from, to);
			s.ConnectTo(c.text);
			s.SetPos(from);
			s.Scan;
			IF s.type = TextMappers.string THEN
				buf := TextModels.CloneOf(c.text);
				f.ConnectTo(buf);
				ObxPhoneDB.LookupByName(s.string$, number);
				f.WriteString(number);
				from := s.start; to := s.Pos() - 1;	(* сканер уже считал символ до строки! *)
				c.text.Delete(from, to);							(*удалить имя*)
				c.text.Insert(from, buf, 0, buf.Length());	(* переместить телефонный номер из буфера в текст *)
				c.SetSelection(from, from + LEN(number$))	(* выбрать телефонный номер*)
			END
		END
	END Do;

END ObxLookup0.

Листинг 5-29. Замена имени на телефонный номер

    Основная идея вышеприведенной команды состоит в том, что вспомогательный текстовый объект используется как буфер для операции. Выделенный текстовый фрагмент сканируется, отсканированная строка используется как ключ для поиска в базе данных телефона и возвращается телефонный номер в виде записи в текстовом буфере, затем первоначальное имя удаляется из текста, затем содержимое буфера помещается на место удаленного имени. В качестве заключительного шага вновь вставленный текст помечается как выделенный. Следует отметить, что процедура текстовой модели Delete укорачивает текст, в то время как процедура Insert удлиняет его. В частности, Insert не перезаписывает имеющийся текст. Insert перемещает текст, то есть удаляет его из источника и вставляет его в назначение. 
    Один из способов сделать упомянутую выше команду удобней заключается в замене выделения на введение символа с клавиатуры. Затем можно написать имя и нажать клавиатурное сокращение для команды. Команда прочитает текст назад от позиции курсора ввода до начала имени и заменит его на подходящий для этого имени номер телефона. Подобный способ является простым способом реализации клавиатурных макросов.
    Перемещение участков текста из одной текстовой модели в другую текстовую модель может быть оптимизировано в случае, если обе текстовые модели имеют одну реализацию. В этом нет необходимости, так как различные реализации типа TextModels.Models могут взаимно существовать в одно время. Два вызова TextModels.dir.New() могут вернуть две правильные текстовые модели, имеющие различные реализации.
    По этой причине в модуле TextModels экспортируется процедура Clone, которая возвращает пустую текстовую модель с типом, в точности соответствующим типу ее параметра. Фактически стандартная реализация текста даже реализует некоторые дополнительные возможности оптимизации при создании текстовой модели с помощью "клонирования", чем при размещении посредством TextModels.dir.New().
    Ниже приводится слегка упрощенное определение TextModels.Model:

TYPE
	Model = POINTER TO ABSTRACT RECORD (Containers.Model)
		(m: Model) NewWriter (old: TextModels.Writer): TextModels.Writer, NEW, ABSTRACT;
		(m: Model) NewReader (old: TextModels.Reader): TextModels.Reader, NEW, ABSTRACT;
		(m: Model) Length (): INTEGER, NEW, ABSTRACT;
		(m: Model) Insert (pos: INTEGER; m0: TextModels.Model;
													beg0, end0: INTEGER), NEW, ABSTRACT;
		(m: Model) InsertCopy (pos: INTEGER; m0: TextModels.Model;
													beg0, end0: INTEGER), NEW, ABSTRACT;
		(m: Model) Delete (beg, end: INTEGER), NEW, ABSTRACT;
		(m: Model) Append (m0: TextModels.Model), NEW, ABSTRACT;
		(m: Model) Replace (beg, end: INTEGER; m0: TextModels.Model;
													beg0, end0: INTEGER), NEW, ABSTRACT;
		(m: Model) SetAttr (beg, end: INTEGER; attr: TextModels.Attributes), NEW, ABSTRACT;
	END;

Листинг 5-30. Определение TextModels.Model

    Практически все процедуры уже были упомянуты. InsertCopy подобна Insert, но вместо перемещения копирует участок текста без изменения источника. Delete удаляет фрагмент текста.
    Insert, InsertCopy и Delete являются элементарными текстовыми операциями. Append и Replace поддерживается для удобства и эффективности: они могут быть выражены в терминах элементарных операций.

dst.Append(src)    dst.Insert(dst.Length(), src, 0, src.Length())
 
То есть она перемещает все содержимое текста-источника в конец текста-назначения, таким образом оставляя текст источник пустым.

t.Replace(b, e, t1, b1, e1)    t.Delete(b, e); t.Insert(b, t1, b1, e1)
  
То есть она перезаписывает часть текста-назначения на некоторую часть текста-источника (который таким образом удаляется). Необходимо отметить, что тексты источника и назначения должны быть разными.
    SetAttr позволяет выставить текстовые атрибуты для всего участка текста.


    5.5 Текстовые сценарии

    После успешного применения команды ObxLookup0.Do для участка текста можно попробовать нечто удивительное: можно отменить команду ("undo"), выполнив команду меню Edit->Undo! С помощью Edit->Redo можно отменить отмену операции. 
    Эта возможность удивительна, так как была реализована только простая процедура Do, но не код для ее отмены или повторного применения. Как случилось, что среда разработки все равно смогла отменить/повторить операцию?
    Это возможно, потому что процедуры текстовой модели, которые изменяют статус модели, такие как Model.Delete и Model.Insert реализованы особым образом. Они не выполняют изменения напрямую. Вместо этого они создают специальный объект операции и регистрируют его в оболочке. Оболочка может вызвать соответствующую процедуру для операции для реализации фактической функциональности выполнения/отмены/повторения. Этот механизм был описан в главе 3.
    Следующий пример показывает последовательность процедур, модифицирующих текст, заключенную в скобки BeginScript и EndScript для того, чтобы сделать последовательность целиком откатываемой. BeginScript возвращает объект сценария (script), он задает имя для всей составной операции как ввод.

MODULE ObxLookup1;

	IMPORT Stores, Models, TextModels, TextMappers, TextControllers, ObxPhoneDB;

	PROCEDURE Do*;
	(** используйте TextCmds.SelectionGuard в качестве охранника команды **)
		VAR c: TextControllers.Controller; buf: TextModels.Model; from, to: INTEGER;
			s: TextMappers.Scanner; f: TextMappers.Formatter; number: ObxPhoneDB.String;
			script:Stores.Operation;
	BEGIN
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			c.GetSelection(from, to);
			s.ConnectTo(c.text);
			s.SetPos(from);
			s.Scan;
			IF s.type = TextMappers.string THEN
				buf := TextModels.CloneOf(c.text);
				f.ConnectTo(buf);
				ObxPhoneDB.LookupByName(s.string$, number);
				f.WriteString(number);
				from := s.start; to := s.Pos() - 1;	(* сканер уже прочел символ до строки! *)
				Models.BeginScript(c.text,"#Obx:Lookup",script);
				c.text.Delete(from, to);							(* удалить имя *)
				c.text.Insert(from, buf, 0, buf.Length());	(* переместить телефонный номер из буфера в текст *)
				Models.EndScript(c.text,script);
				c.SetSelection(from, from + LEN(number$))	(* выбрать телефонный номер *)
			END
		END
	END Do;

END ObxLookup1.

Листинг 5-31. Пример сценария (составной операции)

    В Models.BeginScript задается имя составной операции. Оно отображается с помощью возможностей отображения строк BlackBox с целью отображения этого имени для пользователя (в команде меню undo/redo).
    В моделях ObxLookup0 и ObxLookup1 применяются следующие последовательности операторов для замены имени на номер:

c.text.Delete(from, to);
	c.text.Insert(from, buf, 0, buf.Length());

Объединение этих двух операторов в одну составную операцию (как ожидает пользователь) приводит к необходимости заключения этих двух вызовов в операторные скобки BeginScript/EndScript. В данном специальном случае будет лучше, если применить:

c.text.Replace(from, to, buf, 0, buf.Length());

TextModels.Model.Replace является мощной процедурой, которая представляет собой частный случай операций Insert, Delete и Append (поддерживаются для удобства):

t.Delete(beg, end)	=	t.Replace(beg, end, t, 0, 0)
	t.Insert(pos, t0, beg, end)	=	t.Replace(pos, pos, t0, beg, end)
	t.Append(t0)	=	t.Replace(t.Length(), t.Length(), t0, 0, t0.Length())

Следует отметить, что InsertCopy не может быть выражено в терминах Replace:

t.InsertCopy(pos, t0, beg, end)	#	t.Replace(pos, pos, t0, beg, end)

По этой причине Replace, подобно Insert удаляет текст источника. С другой стороны, InsertCopy является операцией чистого копирования без модификации текста источника (кроме случаев, когда текст-источник и текст-назначения идентичны и позиция текста-назначения лежит в диапазоне текста-источника)


    5.6 Резюме 

    На этом обсуждение текстовой подсистемы BlackBox Component Builder заканчивается. Основной целью этого учебника является обзор связанного комплекса расширений подсистем, использование шаблонов проектирования из предыдущей главы, показ типичных примеров кода текстовой подсистемы.
    Познание текстовой подсистемы не может быть завершено, поскольку остается много деталей, относящихся к документации разных моделей. Тем не менее, главная цель и типичные способы применения должны быть ясны. Так как в начале было изложено немного теории и применялись только простые примеры, базовая структура подсистемы составных контейнеров должна быть ясна уже сейчас.
    Перед тем как дать список примеров из встроенной помощи, которые следует изучить, приведем список всех стандартных модулей текстовой подсистемы (самый нижний модуль, иерархически, самый первый) с указанием важных типов, экспортируемых из них:

module / type	description

	TextModels	 - абстрактная и реализация по умолчанию для текстовых моделей
		Attributes    - атрибуты текстовых элементов (шрифт, цвет, вертикальное смещение)
		Context	   - связь между текстовой моделью и внедренным отображением
		Directory	 - фабрика текстовых моделей
		Model		  - текстовый пользователь, фабрика для считывателей и записывателей
		Reader	    - курьер для элементоориентированного чтения текста
		Writer		  - курьер для элементоориентированной записи текста
  	TextMapper   - абстрактная и реальная реализация текстовых мапперов для символов Component 	
							  Pascal
		Formatter   - отогбражатель для символьноориентированной записи текста
		Scanner	 - отображатель для символьноориентированного чтения текста
	TextRulers	 - абстрактная и реализация по умолчанию для отображения линеек, которые задают 
	                      	атрибуты параграфа
		Attributes	- атрибуты текстовых линеек (табуляция, отступы, сетка и т.п.)
		Directory	 - фабрика для текстовых линеек
		Ruler		   - отображения для линеек
		Style	        - модели линеек

	TextSetters	 - абстрактная и реализация по умолчанию для текстовых установщиков,
	                          обеспечивающих разрыв строки/страницы
	Directory		 - для текстовых установщиков
		Reader		- текстовый отображатель, разделяющий текст по своим полям по текстовым установкам
		                       и определение их размера
		Setter		  - объект, реализующих алгоритм установки текстовых параметров
	TextViews	   - абстрактная и реализация по умолчанию для текстовых отображений
		Directory	 - фабрика текстовых отображений
		View		    - отображение текстовых моделей
TextControllers   - абстрактная и реализация по умолчанию для текстовых контроллеров
		Controller    - контроллер для текстового отображения
		Directory	 - фабрика текстовых контроллеров
	TextCmds	   - командный пакет для наиболее важных текстовых команд

Таблица 5-32.    - таблица модулей и наиболее важных типов для текстовых подсистем

	ObxHello0 - пишет "Hello World" в рабочий журнал
	ObxHello1 - пишет "Hello World" в новый текст
	ObxOpen0 - открывает текстовый документ из файла с применением стандартного диалога открытия
		 файла
	ObxOpen1 - Изменение текстового документа в файле с применением стандартных диалогов открытия
		 и сохранения файлов
	ObxCaps - приводит строку к верхнему регистру с применением составных операций
	ObxDbd - управление отсортированным списком записей
	ObxMMerge - слияние для почты шаблона и базы данных
	ObxLinks - создает текст с гиперссылками
	ObxAscii - обычный текстовый файл

Таблица 5-33. Дополнительные встроенные примеры по текстовой подсистеме