SHX-файлы: отрисовка на TCanvas
Главная »Статьи »AutoCAD и Delphi »SHX-файлы: отрисовка на TCanvas
SHX-файлы: отрисовка на TCanvas

Данная статья продолжает изложение, начатое в статье SHX-файлы: Чтение, расширяя созданные там классы. Для отрисовки используются описания кодов форм, описанные в справке AutoCAD в разделе Create Shape Definition Files (Создание определений формы).

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

FX, FY: Double;
FPenDown: Boolean;
FGlyphScale: Double;
FXmin, FXmax, FYmin, FYmax: Double;
FVert: Boolean;

Все примитивы глифа преобразовываем в отрезки, тогда метод непосредственно отрисовки выгдялит просто:

procedure TShxShape.DrawLine(ACanvas: TCanvas);
var
  iX, iY: Integer;
begin
  // Отрегулировать границы охватывающего прямоугольника
  AdjustBounds(FX, FY);
  // Отрисовать
  if ACanvas = nil then Exit;
  iX:= Round(FX);
  iY:= Round(FY);
  if FPenDown then
  begin
    ACanvas.LineTo(iX, iY);
    ACanvas.Pixels[iX, iY]:= ACanvas.Pen.Color;
  end else
    ACanvas.MoveTo(iX, iY);
end;

Метод отрисовки глифа:

procedure TShxShape.DrawToCanvas(ACanvas: TCanvas; var AX, AY: Double; AScale: Double);
var
  i: Word;
begin
  FVert:= False;
  FGlyphScale:= AScale;
  FXmin:= MaxDouble;
  FXmax:= -MaxDouble;
  FYmin:= MaxDouble;
  FYmax:= -MaxDouble;
  FX:= AX;
  FY:= AY;
  // Вначале переместить курсор в точку (FX, FY)
  FPenDown:= False;
  DrawLine(ACanvas);
  FPenDown:= True;
  // Отрисовать глиф
  FGlyphHighIndex:= High(FGlyph);
  i:= 0;
  while i < FGlyphHighIndex do
  begin
    SpecialCodeHandler(ACanvas, i);
  end;
  if (i > FGlyphHighIndex) or ((i = FGlyphHighIndex) and (FGlyph[FGlyphHighIndex] <> GLYPH_SHAPE_END)) then
    raise Exception.CreateFmt(rsUnexpectedGlyphEnd, [i, FGlyphHighIndex]);
  // Если символ = LF, то обнулить коорд. X
  if FNumber = LF then
    FX:= 0.0 - FY;
  // Вернуть координаты по завершении глифа
  AX:= FX;
  AY:= FY;
  // Если высота охват. прямоугольника = 0, то установить ее в соотв. с высотой шрифта
  if IsZero(FYmax - FYmin) then
    FYmin:= FFont.Above * FGlyphScale;
  // Отрегулировать границы охватывающего прямоугольника
  FFont.AdjustBounds(FXmin, FYmin);
  FFont.AdjustBounds(FXmax, FYmax);
  FFont.GetBounds(FXmin, FXmax, FYmin, FYmax);
end;

Метод принимает координаты, в которых глиф должен быть отрисован и возвращает в этих же переменных текущие координаты после отрисовки. Также метод принимает значение масштаба. Это позволяет отрисовывать глифы, ссылающиеся на другие глифы (спецкод 0x07).

Все команды и данные для отрисовки выполняются методом SpecialCodeHandler:

procedure TShxShape.SpecialCodeHandler(ACanvas: TCanvas; var Index: Word);
var
  SubShapeNum: Word;
  OldPenDown: Boolean;
begin
  case FGlyph[Index] of
    $0: Inc(Index); // конец описания формы
    $1: begin       // включение режима отрисовки (опускание пера)
          FPenDown:= True;
          Inc(Index);
        end;
    $2: begin       // отключение режима отрисовки (поднятие пера)
          FPenDown:= False;
          Inc(Index);
        end;
    $3: begin       // деление длин векторов на следующий байт
          if (Index + 1) < FGlyphHighIndex then
          begin
            if not FVert then
              FGlyphScale:= FGlyphScale / FGlyph[Index + 1];
          end else
            raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index, FGlyph[Index], FGlyphHighIndex]);
          Inc(Index, 2);
          if FVert then FVert:= False;
        end;
    $4: begin       // умножение длин векторов на следующий байт
          if (Index + 1) < FGlyphHighIndex then
          begin
            if not FVert then
              FGlyphScale:= FGlyphScale * FGlyph[Index + 1];
          end else
            raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index, FGlyph[Index], FGlyphHighIndex]);
          Inc(Index, 2);
          if FVert then FVert:= False;
        end;
    $5: begin       // занесение текущей позиции в стек
          if not FVert then
            PosStack.Push(TPosition.Create(FX, FY));
          Inc(Index);
          if FVert then FVert:= False;
        end;
    $6: begin       // восстановление текущей позиции из стека
          // Проверяем пустоту стека (символ 13 шрифта "bold.shx" содержит
          // всего одну команду - $6, а символ 1 одну команду - $5)
          if not FVert and (PosStack.Count > 0) then
          begin
            FX:= PosStack.Peek.X;
            FY:= PosStack.Peek.Y;
            PosStack.Pop;
            OldPenDown:= FPenDown;
            FPenDown:= False;
            DrawLine(ACanvas);
            FPenDown:= OldPenDown;
          end;
          Inc(Index);
          if FVert then FVert:= False;
        end;
    $7: begin       // отрисовка субформы, номер которой определяется следующим байтом (для UNIFONT - двумя)
          if FFont.FFontKind = fkUniFont then
          begin
            if (Index + 2) < FGlyphHighIndex then
              SubShapeNum:= (FGlyph[Index + 1] shl 8) + FGlyph[Index + 2]
            else
              raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index, FGlyph[Index], FGlyphHighIndex]);
            Inc(Index, 3);
          end else
          begin
            if (Index + 1) < FGlyphHighIndex then
              SubShapeNum:= FGlyph[Index + 1]
            else
              raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index, FGlyph[Index], FGlyphHighIndex]);
            Inc(Index, 2);
          end;
          if not FVert then
          begin
            FFont[FFont.GetShapeIndex(SubShapeNum)].DrawToCanvas(ACanvas, FX, FY, FGlyphScale);
            FPenDown:= False; // сброс поднятия пера после отрисовки субформы
          end;
          if FVert then FVert:= False;
        end;
    $8: begin       // смещение по осям X-Y, заданное следующими двумя байтами
          if (Index + 2) < FGlyphHighIndex then
          begin
            if not FVert then
            begin
              FX:= FX + Shortint(FGlyph[Index + 1]) * FGlyphScale;
              FY:= FY + Shortint(FGlyph[Index + 2]) * FGlyphScale;
              DrawLine(ACanvas);
            end;
          end else
            raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index, FGlyph[Index], FGlyphHighIndex]);
          Inc(Index, 3);
          if FVert then FVert:= False;
        end;
    $9: begin       // ряд из нескольких смещений по осям X-Y, оканчивающийся на (0,0)
          Inc(Index, 2); // сейчас указывает на dY
          while True do
          begin
            if (Index >= FGlyphHighIndex) then
              raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index - 2, FGlyph[Index - 2], 
                                                                     FGlyphHighIndex]);
            if (FGlyph[Index - 1] = 0) and (FGlyph[Index] = 0) then // [(dX,dY) = (0,0)]?
            begin
              Inc(Index);
              Break;
            end;
            if not FVert then
            begin
              FX:= FX + Shortint(FGlyph[Index - 1]) * FGlyphScale;
              FY:= FY + Shortint(FGlyph[Index]) * FGlyphScale;
              DrawLine(ACanvas);
            end;
            Inc(Index, 2);
          end;
          if FVert then FVert:= False;
        end;
    $A: begin       // октантная дуга, заданная следующими двумя байтами (R, DSC)
          if (Index + 2) < FGlyphHighIndex then
          begin
            if not FVert then        // радиус,         [направление, начальный октант, кол. октантов]
              DrawOctantArc(ACanvas, FGlyph[Index + 1], FGlyph[Index + 2]);
          end else
            raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index, FGlyph[Index], FGlyphHighIndex]);
          Inc(Index, 3);
          if FVert then FVert:= False;
        end;
    $B: begin       // дробная дуга, заданная следующими пятью байтами
          if (Index + 5) < FGlyphHighIndex then
          begin
            if not FVert then            // StartOffset,   EndOffset,         HighRadius
              DrawFractionalArc(ACanvas, FGlyph[Index + 1], FGlyph[Index + 2], FGlyph[Index + 3], 
                                FGlyph[Index + 4], FGlyph[Index + 5]); // LowRadius, DSC
          end else
            raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index, FGlyph[Index], FGlyphHighIndex]);
          Inc(Index, 6);
          if FVert then FVert:= False;
        end;
    $C: begin       // дуга, заданная смещением X-Y и прогибом (dX,dY,Bulge)
          if (Index + 3) < FGlyphHighIndex then
          begin
            if not FVert then       // dX,                dY,                Bulge
              DrawBulgeArc(ACanvas, FGlyph[Index + 1], FGlyph[Index + 2], FGlyph[Index + 3]);
          end else
            raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index, FGlyph[Index], FGlyphHighIndex]);
          Inc(Index, 4);
          if FVert then FVert:= False;
        end;
    $D: begin       // несколько дуг, заданных прогибом (dX,dY,Bulge)
          Inc(Index, 3); // сейчас указывает на Bulge
          while True do
          begin
            if (Index > FGlyphHighIndex) then
              raise Exception.CreateFmt(rsUnexpectedSpecialCodeFmt, [Index - 3, FGlyph[Index - 3], 
                                                                     FGlyphHighIndex]);
            if (FGlyph[Index - 2] = 0) and (FGlyph[Index - 1] = 0) then // [(dX,dY) = (0,0)]?
              Break;
            if not FVert then       // dX,                dY,                Bulge)
              DrawBulgeArc(ACanvas, FGlyph[Index - 2], FGlyph[Index - 1], FGlyph[Index]);
            Inc(Index, 3);
          end;
          if FVert then FVert:= False;
        end;
    $E: begin // обработка следующей команды только для вертикального текста
          FVert:= True;
          Inc(Index);
        end;
    else begin // длина вектора и код направления
      if not FVert then
        DrawLenDir(ACanvas, (FGlyph[Index] and $F0) shr 4, FGlyph[Index] and $0F);
      Inc(Index);
      if FVert then FVert:= False;
    end;
  end;
end;
Заметки "на полях":
  1. По умолчанию перо всегда опущено;
  2. Когда восстанавливаем позицию из стека, то перемещаемся в нее с поднятым пером;
  3. Для кода 9:
    • смещения не могут быть (0,0), т.к. эта последовательность означает завершение команды;
    • допустима комбинация 9,(0,0), т.е. пустая последовательность смещений.
  4. Для кода 11:
    • для последнего байта (DSC - направление, начальный октант, кол. октантов) параметр "кол. октантов" означает именно кол. октантов, в которое вмещается дуга.
  5. Для кода 13:
    • не допустима комбинация 13,(0,0), т.е. пустая последовательность (dX,dY,Bulge);
    • минимальная допустимая последовательность 13,(dX,dY,Bulge),(0,0).

В методе обновляются текущие координаты и вызываются методы отрисовки. Класс стека координат (от TObjectStack) тривиальный, см. в прилагаемых исходниках. Для отрисовки дуг создан универсальный метод DrawArc; остальные методы (DrawOctantArc, DrawFractionalArc, DrawBulgeArc) находят для него параметры и вызывают DrawArc.

// StartAngle, SweepAngle - в градусах.
// StartAngle д.б. задан в соответствии с направлением отсчета - CW:
// если CW=False - против часовой;
// если CW=True - по часовой.
procedure TShxShape.DrawArc(ACanvas: TCanvas; CenterX, CenterY, Radius: Double;
  CW: Boolean; StartAngle, SweepAngle: Double);
var
  OldPenDown: Boolean;
  EndX, EndY: Double;
  StepCnt, i: Integer;
  BeginAngle, FlareAngle, ArcLength, ArcSegmentSize, Sin, Cos: Double;
begin
  BeginAngle:= DegToRad(StartAngle); // начальный угол, рад.
  FlareAngle:= DegToRad(SweepAngle); // угол раскрыва дуги, рад.
  ArcLength:= Radius * FlareAngle;   // длина дуги
  // Если заданная дуга по часовой стрелке: запомнить точку начала дуги против часовой
  // стрелки, после построения восстановить ее - это будет точка конца заданной дуги
  if CW then
  begin
    BeginAngle:= 2 * Pi - BeginAngle - FlareAngle;

    SinCos(BeginAngle, Sin, Cos);
    EndX:= CenterX + Radius * Cos;
    EndY:= CenterY + Radius * Sin;
    // Встали в точку конца заданной дуги (т.е. точку начала дуги против часовой стрелки)
    OldPenDown:= FPenDown;
    FPenDown:= False;
    FX:= EndX;
    FY:= EndY;
    DrawLine(ACanvas);
    FPenDown:= OldPenDown;
  end;
  // Длина сегмента (мин. кол. сегментов = 3)
  ArcSegmentSize:= ArcLength / Max((FFont.ArcSmoothness * SweepAngle) / 360.0, 3.0);
  StepCnt:= Trunc(Ceil(ArcLength / ArcSegmentSize));
  for i:= 1 to StepCnt do
  begin
    SinCos(BeginAngle + FlareAngle * i / StepCnt, Sin, Cos);
    FX:= CenterX + Radius * Cos;
    FY:= CenterY + Radius * Sin;
    // Добавить точку
    DrawLine(ACanvas);
  end;
  // Восстановить точку конца заданной дуги
  if CW then
  begin
    FPenDown:= False;
    FX:= EndX;
    FY:= EndY;
    DrawLine(ACanvas);
    FPenDown:= OldPenDown;
  end;
end;

Свойство шрифта ArcSmoothness задает количество сегментов, на которые разбивается дуга в полную окружность. Методы DrawOctantArc, DrawFractionalArc, DrawBulgeArc и DecodeOctant:

// Если знак "+", то байт DSC = (S * 16 + C), иначе DSC = (S * 16 + C) or $80
procedure DecodeOctant(DSC: Byte; var S, C: Byte; var CW: Boolean);
var
  tmp: Shortint;
  b: Byte;
begin
  tmp:= Shortint(DSC);
  CW:= ((tmp and $80) <> 0);
  b:= Byte(tmp);
  S:= (b and $70) shr 4;
  C:= b and $07;
end;

procedure TShxShape.DrawOctantArc(ACanvas: TCanvas; R, DSC: Byte);
var
  S, C: Byte;  // начальный октант, количество охватываемых октантов
  CW: Boolean; // True - по часовой стрелке; False - против часовой стрелки
  CenterX, CenterY, Radius: Double;
  StartAngle, SweepAngle: Integer; // в градусах
begin
  DecodeOctant(DSC, S, C, CW);
  Radius:= R * FGlyphScale;
  GetOctantArcParams(FX, FY, Radius, S, C, CW, CenterX, CenterY, StartAngle, SweepAngle);
  DrawArc(ACanvas, CenterX, CenterY, Radius, CW, StartAngle, SweepAngle);
end;

procedure TShxShape.DrawFractionalArc(ACanvas: TCanvas; StartOffset, EndOffset,
  HighRadius, LowRadius, DSC: Byte);
var
  S, C: Byte;  // начальный октант, конечный октант
  CW: Boolean; // True - по часовой стрелке; False - против часовой стрелки
  CenterX, CenterY, Radius: Double;
  StartAngle, SweepAngle: Double; // в градусах
begin
  DecodeOctant(DSC, S, C, CW);
  Radius:= ((HighRadius shl 8) + LowRadius) * FGlyphScale;
  GetFractionalArcParams(FX, FY, Radius, S, C, StartOffset, EndOffset, CW, CenterX, CenterY, StartAngle, 
                         SweepAngle);
  DrawArc(ACanvas, CenterX, CenterY, Radius, CW, StartAngle, SweepAngle);
end;

procedure TShxShape.DrawBulgeArc(ACanvas: TCanvas; dX, dY, Bulge: Byte);
var
  OfsX, OfsY: Double;
  b: Shortint;
  CenterX, CenterY, Radius: Double;
  CW: Boolean;
  StartAngle, SweepAngle: Double;
begin
  OfsX:= Shortint(dX) * FGlyphScale;
  OfsY:= Shortint(dY) * FGlyphScale;
  b:= Shortint(Bulge);
  if b <> 0 then // если это дуга - рисовать ее
  begin
    GetBulgeArcParams(FX, FY, OfsX, OfsY, b, 127.0, CenterX, CenterY, Radius,
      CW, StartAngle, SweepAngle);
    DrawArc(ACanvas, CenterX, CenterY, Radius, CW, StartAngle, SweepAngle);
  end else // если это прямой сегмент - рисовать прямую
  begin
    FX:= FX + OfsX;
    FY:= FY + OfsY;
    DrawLine(ACanvas);
  end;
end;

Методы получения параметров различных видов дуг

// Углы в градусах.
// StartOctant, OctantCount = [0..7]. OctantCount = 0 означает 8 октантов.
procedure GetOctantArcParams(BeginX, BeginY, Radius: Double;
  StartOctant, OctantCount: Byte; Clockwise: Boolean; var CenterX, CenterY: Double;
  var StartAngle, SweepAngle: Integer);
var
  Sin, Cos: Double;
begin
  StartAngle:= StartOctant * 45;
  SinCos(DegToRad(StartAngle), Sin, Cos);
  CenterX:= BeginX - Radius * Cos;
  CenterY:= BeginY - Radius * Sin;
  if OctantCount = 0 then
    SweepAngle:= 360
  else
    SweepAngle:= 45 * OctantCount;
  if Clockwise then
    StartAngle:= 360 - StartAngle;
end;

// Углы в градусах.
procedure GetFractionalArcParams(BeginX, BeginY, Radius: Double;
  StartOctant, OctantCount, StartOffset, EndOffset: Byte; Clockwise: Boolean;
  var CenterX, CenterY, StartAngle, SweepAngle: Double);
var
  S: Byte;
  StartAngleInRad, StartBeta, EndBeta, EndAngle: Double;
  Sin, Cos: Extended;
begin
  StartBeta:= StartOffset * 45.0 / 256.0;
  EndBeta:= EndOffset * 45.0 / 256.0;

  if not Clockwise then // против часовой
  begin
    S:= StartOctant;
    StartAngle:= S * 45.0 + StartBeta;
    if OctantCount <> 0 then
    begin
      if EndOffset = 0 then
        EndAngle:= (StartOctant + OctantCount) * 45.0 + EndBeta
      else
        EndAngle:= (StartOctant + OctantCount - 1) * 45.0 + EndBeta;
      SweepAngle:= EndAngle - StartAngle;
    end else
      SweepAngle:= 7.0 * 45.0 + EndBeta - StartBeta;
  end else
  begin  // по часовой
    S:= ((8 - StartOctant) mod 8);
    StartAngle:= S * 45.0 + StartBeta;
    if OctantCount = 0 then
    begin
      EndAngle:= S * 45.0 + (OctantCount - 1) * 45.0 + EndBeta
    end else
    begin
      if EndOffset = 0 then
        EndAngle:= S * 45.0 + OctantCount * 45.0 + EndBeta
      else
        EndAngle:= S * 45.0 + (OctantCount - 1) * 45.0 + EndBeta;
    end;
    if EndAngle > 360.0 then
      EndAngle:= EndAngle - 360.0;

    if EndAngle < StartAngle then
      SweepAngle:= 360.0 + EndAngle - StartAngle
    else
      SweepAngle:= EndAngle - StartAngle;
  end;

  if SweepAngle > 360.0 then
    SweepAngle:= 360.0;

  // Для расчета центра пересчитаем в угол против часовой
  if Clockwise then
    StartAngleInRad:= DegToRad(360.0 - StartAngle)
  else
    StartAngleInRad:= DegToRad(StartAngle);
  SinCos(StartAngleInRad, Sin, Cos);
  CenterX:= BeginX - Radius * Cos;
  CenterY:= BeginY - Radius * Sin;
end;

// Углы в градусах.
// Для шрифта Factor = 127, т.к. Bulge = (2 * H / D) * 127, в остальных случаях Factor = 1.
procedure GetBulgeArcParams(BeginX, BeginY, dX, dY, Bulge, Factor: Double;
  var CenterX, CenterY, Radius: Double; var Clockwise: Boolean;
  var StartAngle, SweepAngle: Double);
var
  EndX, EndY, MidX, MidY: Double;
  Chord, HalfChord, H, S: Double;
begin
  Clockwise:= (Bulge < 0.0);
  // Точка конца дуги
  EndX:= BeginX + dx;
  EndY:= BeginY + dy;
  // Длина хорды
  Chord:= Sqrt((EndX - BeginX) * (EndX - BeginX) + (EndY - BeginY) * (EndY - BeginY));
  // Половина длины хорды
  HalfChord:= Chord / 2.0;
  // Стрелка (sagitta)
  H:= Abs(Bulge) * Chord / 2.0 / Factor;
  // Радиус
  Radius:= (HalfChord * HalfChord + H * H) / H / 2.0;
  // Координаты точки по-середине между концами дуги
  MidX:= (BeginX + EndX) / 2.0;
  MidY:= (BeginY + EndY) / 2.0;
  // Знак в уравнениях центра
  SweepAngle:= ArcTan(Abs(Bulge / Factor)) * 4.0; // внутренний угол дуги
  if not Clockwise then // CCW - против часовой стрелки
  begin
    if SweepAngle >= Pi then
      S:= -1.0
    else
      S:= +1.0;
  end else              // CW - по часовой стрелке
  begin
    if SweepAngle >= Pi then
      S:= +1.0
    else
      S:= -1.0;
  end;
  // Координаты центра
  CenterX:= MidX + S * Sqrt(Radius * Radius - HalfChord * HalfChord) * (BeginY - EndY) / Chord;
  CenterY:= MidY + S * Sqrt(Radius * Radius - HalfChord * HalfChord) * (EndX - BeginX) / Chord;
  // Углы
  StartAngle:= ArcTan2(BeginY - CenterY, BeginX - CenterX);
  if (StartAngle < 0.0) then
  begin
    if Clockwise then
      StartAngle:= Abs(StartAngle)
    else
      StartAngle:= TWO_PI + StartAngle;
  end else
  begin
    if Clockwise then
      StartAngle:= TWO_PI - StartAngle;
  end;
  StartAngle:= RadToDeg(StartAngle);
  SweepAngle:= RadToDeg(SweepAngle);
end;

Документация AutoCAD не всегда полна и точна, в частности при описании Fractional Arc (дробной дуги). Эксперименты показали следующее:

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

Имя

Тип

Кол. символов*

Особенности

simple_shapes.shx

Файл форм (не шрифта)

4

Формы: 170, 190, 210, 230. Формы имеют имена: QWER1, QWER2, QWER3, DBOX. Форма DBOX описана в справке AutoCAD (Vector Length and Direction Code).

shapes_names.shx

Файл шрифта форм

6

Формы имеют имена

shape_digits.shx

Файл шрифта форм

12

Символы: перевод строки, пробел, цифры 0..9.
Номера символов: 10, 32, 48..57.

shape_digits_shape0_not first.shx

Файл шрифта форм

12

Файл создан из исходного shp-файла, в котором символ 0 не был первым.

shapes_names_back_shapes_order.shx

Файл шрифта форм

12

Файл создан из исходного shp-файла, в котором символы расположены в обратном порядке (57..48, 32, 10).

uni_digits.shx

Файл шрифта Unicode

12

Тоже что «shape_digits.shx», но Unicode

SYMBOL_S.shx

Файл шрифта Unicode

3

Символы: перевод строки, пробел, S.
Номера символов: 10, 32, 83. Символ S описан в справке AutoCAD (Bulge-Specified Arcs), рисуется кодом 13.

dim.shx

Файл шрифта форм

100

Нет русских символов. Символ 127 (градус) выполнен октантной дугой. Символ 255 содержит команды: уменьшения масштаба, сдвига вверх и ссылки (спецкод 7) на символы; получается «(C) 1992 SOFT DESK» верхним индексом.

romans.shx

Файл шрифта Unicode

299

Символы 128, 0x20A0 содержат ссылки (спецкод 7) на символ 0x20AC («евро»), который выполнен с использованием 2-х «Octant Arc»

bold.shx

Файл шрифта форм

103

Нет русских символов. Содержит множество «Octant Arc» и «Fractional Arc»

g12f13.shx

Файл шрифта Unicode

114

Нет русских символов. Содержит множество «Bulge-Specified Arcs»

* — для шрифтов это настоящее количество символов, т.е. без учета символа 0.

В прилагаемом примере построено приложение, реализующее описанный функционал:

К статье прилагаются файлы:


Внимание! Запрещается воспроизведение данной статьи или ее части без согласования с автором. Если вы желаете разместить эту статью на своем сайте или издать в печатном виде, свяжитесь с автором.
Автор статьи: Вершинин И.В.