この記事は機械翻訳されたものです。

UI 最前線

Silverlight でのフォントのメトリック

Charles Petzold

コード サンプルのダウンロード

Charles Petzoldほとんどのグラフィカルなプログラミング環境では、クラスまたはフォント メトリックを取得するための機能があります。これらのフォントのメトリックを特定のフォントで表示すると、テキスト文字のサイズに関する情報を提供します。 最低限でも、フォント メトリック情報には、個々 のすべての文字と、すべての文字に共通する高さの幅が含まれます。 内部的には、アクセスが高速ですのでこれらの幅可能性がありますを配列に格納されます。 フォント メトリックは、段落とページ内のテキストのレイアウトは重要ではありません。

残念なことに、Silverlight は 1 つのグラフィカルな環境であるしないフォント メトリックをアプリケーション プログラムの開発者に提供します。 レンダリングの前にテキストのサイズを取得したい場合は、TextBlock では、もちろん、同じ要素のテキストのレンダリングに使用されますを使用する必要があります。 内部的には、TextBlock は言うまでもなくフォント メトリックへのアクセスがあります。 それ以外の場合、アイデアの大きさのテキストがあると想定されることはできません。

実際には、テキストを表示することがなく、テキストの寸法を提供する TextBlock を説得するは簡単です。 だけでは、TextBlock 要素をインスタンス化、FontFamily、FontSize、FontStyle、FontWeight、およびテキストのプロパティを初期化後、ActualWidth および ActualHeight プロパティを照会します。 いくつかの Silverlight 要素とは異なり、この TextBlock 子パネルの枠線を作成する必要はありません。 また、親メジャー メソッドを呼び出す必要があります操作を行います。

このプロセスを高速化するには、TextBlock を使用して、文字の幅の配列を構築することができ、この配列を使用して、従来のフォント メトリックを模倣するのには、します。 どのように、ここで行う方法について説明します。

すべてはこの本当に必要か。

多くのアプリケーションでは、TextBlock は不要になりますので、Silverlight のほとんどのプログラマがない場合のフォントのメトリックの mourn されていません。 TextBlock は非常に汎用性が高まります。 Inlines プロパティを使用すると、1 つの TextBlock 要素斜体と太字のテキストとものテキストに異なるフォント ファミリやフォント サイズの組み合わせを表示できます。 TextBlock はまた長いテキストの段落を作成するのには、複数の行に折り返すことができます。 それは私が過去の 2 部では、このコラムで簡単な電子ブック リーダー Windows Phone 7 を作成する使用したこのテキストの折り返し機能です。

以前の問題では、自分の携帯電話上のジョージ ・ エリオットの小説"Middlemarch"を閲覧することができます MiddlemarchReader と呼ばれるプログラムを紹介します。 そのプログラムで実験を実行してください: 実際の Windows Phone 7 デバイスの新しいコピーを展開します. (必要な場合は、まず電話で既に可能性がありますいずれかのバージョン アンインストール。)チャプターの一覧を取得するには、アプリケーション バーのボタンを押してみましょう。 章 IV を選択します。 章 IV の最初のページから、指を画面左から最終章の 3 番目のページを参照してくださいして、秒のカウントを開始する権利を法:「ミシシッピの 1 つ、2 つのミシシッピ…」

自分の携帯電話が携帯のような何かの場合は、章 IV の先頭からページング章世の終わりまでの約 10 秒かかることがわかります。 すべて 10 秒である非常に長いページの単純なターンの賛成できると思います。

この待機場を改ページ調整の特徴です。 章の最後のページを表示するすべての前のページに章まず改ページ調整されるようにする必要があります。 この列の前の「で私使用した改ページ調整の方法 grossly に効率的ではありませんし、この例では、証明前述。

段落が複数のページをまたぐ場合は全体の段落または段落の一部をレンダリングするのに TextBlock のテキストの折り返し機能を低速改ページ調整の方法を使用します。 段落がページに収まるように大きすぎる場合は、コードが同じになるまで、段落の最後の単語から lopping を起動します。 各単語を削除すると、Silverlight TextBlock、re-measure する必要があります、この多くの時間が必要です。

間違いなく私の改ページ調整のロジックを修正する必要があります。 改ページ調整アルゴリズム各段落を個々 の単語に分割、各単語のサイズを取得および、独自の単語の位置決めと行の折り返しのロジックを実行します。

以前の電子ブック リーダーは、私このコラムで紹介した各段落の段落の一部のページだけで 1 つの TextBlock、ありこれらの TextBlock 要素が StackPanel の子であります。 このコラムで説明する電子ブック リーダー、ページ上のすべての単語独自の TextBlock あり各 TextBlock キャンバス上の特定の位置に配置されます。 これら複数の TextBlock 要素がページをレンダリングするために Silverlight の少しの時間を必要とするが、ページ レイアウト非常に speeded です。 実験 2 秒間の TextBlock 要素では、各単語を測定するときに、従来のフォント メトリックのような配列内の文字幅のキャッシュの 0.5 秒に短縮され、厄介なものの 10 秒間のページ遷移 MiddlemarchReader が少ないことを示しています。

新しい本です。 この記事のダウンロード可能な Visual Studio のソリューション PhineasReader と呼ばれ、アンソニー Trollope 最も愛する架空のキャラクター、国会議員は、アイリッシュ メンバーのだのいずれかの物語「Phineas Finn」(1869) を読み取ることができます。 もう一度、私からのプロジェクトの医薬ダウンロード プレーン テキスト ファイルを使用した (gutenberg.org)。

FontMetrics クラス

コンピューターのフォントを最初にデザインする場合は、フォント デザイナー「em サイズ」と呼ばれるが、番号を選択します。用語正方形のブロックの種類、大文字の M をしたし、他のすべての文字の相対的な幅と高さ、M のサイズを決定する olden 日からのものです。

TrueType フォントの多くは、2,048「デザイン ユニット」の em サイズで設計されています文字の高さの整数になるようにサイズが十分である-は、通常、分音記号に対応するため、2,048 より- と文字全体の幅がすべて整数にも。

Windows Phone 7 では、サポートされているフォントのいずれかを使用して、テキスト ブロックを作成します。 FontSize プロパティが 2,048 に設定がいる場合は、ActualWidth 整数に関係なく、テキスト プロパティにどのような文字セットを返すことに気付くでしょう。 (ActualHeight も Segoe WP 太字フォントおよびノート ブック コンピューターのユーザー インターフェイスの既定のフォントは整数です。 これら 2 つの名前を同じフォントを参照してくださいし、高さは 2,457.6 です。 この不一致の理由を知らないです。)

文字の高さを取得して、FontSize プロパティに基づいて幅を 2,048 に設定すると、その高さや幅のフォント サイズを単に拡張できます。

図 1 作成 FontMetrics クラスを示しています。 複数のフォントを処理する必要がある場合は、(標準または太字) FontMetrics の別のインスタンスでは、各フォント ファミリ、フォント スタイル (標準または斜体) やフォントの太さを保持します。 これら FontMetrics インスタンスを参照するにはほとんどが IEquatable インターフェイスを実装する Font クラスが作成されているため、辞書から、ため、ディクショナリのキーとして適しています。 私の電子ブック リーダー Windows Phone 7 のデフォルトのフォントをベース FontMetrics インスタンスが 1 つだけで済みます。

図 1 FontMetrics クラス

public class FontMetrics
{
  const int EmSize = 2048;
  TextBlock txtblk;
  double height;
  double[][] charWidths = new double[256][];

  public FontMetrics(Font font)
  {
    this.Font = font;
            
    // Create the TextBlock for all measurements
    txtblk = new TextBlock
    {
      FontFamily = this.Font.FontFamily,
      FontStyle = this.Font.FontStyle,
      FontWeight = this.Font.FontWeight,
      FontSize = EmSize
    };

    // Store the character height
    txtblk.Text = " ";
    height = txtblk.ActualHeight / EmSize;
  }

  public Font Font { protected set; get; }

  public double this[char ch]
  {
    get
    {
      // Break apart the character code
      int upper = (ushort)ch >> 8;
      int lower = (ushort)ch & 0xFF;

      // If there's no array, create one
      if (charWidths[upper] == null)
      {
        charWidths[upper] = new double[256];

        for (int i = 0; i < 256; i++)
          charWidths[upper][i] = -1;
      }

      // If there's no character width, obtain it
      if (charWidths[upper][lower] == -1)
      {
        txtblk.Text = ch.ToString();
        charWidths[upper][lower] = txtblk.ActualWidth / EmSize;
      }
      return charWidths[upper][lower];
    }
  }

  public Size MeasureText(string text)
  {
    double accumWidth = 0;

    foreach (char ch in text)
      accumWidth += this[ch];

    return new Size(accumWidth, height);
  }

  public Size MeasureText(string text, int startIndex, int length)
  {
    double accumWidth = 0;

    for (int index = startIndex; index < startIndex + length; index++)
      accumWidth += this[text[index]];

    return new Size(accumWidth, height);
  }
}

もともと私に関する共通の em サイズ 2,048 に関する自分の知識を活用しなどの 16 ビット整数を整数としてすべての文字の幅を格納と思いました。 しかし、倍精度浮動小数点値として格納して安全性を確保しました。 私からは、実際には 1 の FontSize を適切な値を格納するよう FontMetrics ActualWidth および ActualHeight の値 2,048 で、分割することをにしました。 これにより、クラスを使用して目的の FontSize 値を乗算する任意のプログラムを簡単になります。

プロジェクト医薬の普通のテキスト ファイルは Unicode 値 256 未満の文字のみを使用します。 したがって、FontMetrics クラスは、256 個の値の単純な配列で、必要な文字幅を格納できます。 256 以上の文字コードを含むテキストにこのクラスを使用することができますので、私はそれより柔軟な必要があるが私が最後のものです 64,536 の倍精度浮動小数点数値を格納するための十分な配列を割り当てることを知っていました。 .5MB のフォント メトリックをメモリです。

代わりに、私、ジャグ配列を使用します。 CharWidths という名前の配列は、それぞれ 256 の double 値の配列が、256 の要素があります。 2 つの 8 ビットのインデックスには、16 ビットの文字コードに分割されます。 上位バイト 256 の double 値の配列を作成] をポイントし、[下位バイトは、文字コードのインデックスの配列を取得するのには、charWidths 配列のインデックスを作成します。 しかし、必要な場合のみ、必要に応じて個々 の文字の幅を取得するとこれらの double 値の配列にのみ作成されます。 このロジックは、FontMetrics クラスのインデクサーで行われクラスに必要なストレージの量を削減しての両方でまったく使用されない文字を処理する不要な短縮できます。。

MeasureText の 2 つの方法は、文字列のサイズや、大きな文字列の部分文字列を取得します。 これら 2 つの方法は、だけで目的のフォント サイズを乗算することによって拡張できます 1、FontSize の値を返します。

TextBlock 要素は、通常、調整ピクセルの境界では、UIElement クラスで定義されている、UseLayoutRounding プロパティの既定値であるため。 アンチ エイリアスの矛盾を回避するためテキスト」は、ピクセル配置読みやすくするために役立ちます。 MeasureText からはフォント サイズによって取得された値を乗算した後、Math.Ceiling メソッドで値を渡す必要があります。 次の整数のピクセルに切り上げた値が表示されます。

書式 fancier

前の電子ブック リーダーのようにほとんどの実際のグラント プログラムの PageProvider クラスでに発生します。 このクラスは 2 つの主なジョブがあります。 ファイルの各行が 1 行の段落、および改ページ調整に連結するには、医薬のプロジェクト ファイルの前処理します.

FontMetrics 256 以上の文字コードをテストするには、これまでよりも、若干のプリプロセスを実行するために私を決定します。 最初に、私「高級な引用符」で二重引用符 (ASCII コード 0x22) 標準的な交換 (Unicode 0x201C および 0x201D) では各段落内の 2 つのコードの代替します。 また、著者を凝らしたビクトリア em ダッシュを大量に使用する傾向がある-多くの場合、このような語句を区切るために- とは医薬のプロジェクト ファイルとダッシュのペア。 ほとんどの場合、私これらのダッシュのペア Unicode スペースで囲まれた 0x2014 の改行を簡単に交換します。

私の新しいプリプロセス ロジックも同じインデントで連続する行を処理します。 私はより安全な方法で処理しようし、して、文字やその他のインデントを設定資料、テキスト、多くの場合、これらのインデントが設定された線を構成します。 [改ページ調整しながら、[はインデントされていないすべての段落の最初の段落が章は、私は、通常、章のタイトルにはしましたは、先頭行のインデントを開始しました。

このインデントのロジックの全体的な効果を示しています図 2

A Page from PhineasReader Showing Paragraph Indenting

図 2 から PhineasReader の段落のインデントを示すページ

改ページ調整および構成

PageProvider 経由で TextBlock が以前を実行して、レイアウトの大部分を取得するため、改ページのロジックは少し過ぎるこの雑誌のページとなっています。 非常に簡単です。 プロジェクト医薬テキストを構成するすべての段落にリストの ParagraphInfo のオブジェクトとして格納されます。 書式設定されたブック リストの ChapterInfo オブジェクトではほとんどの場合、BookInfo オブジェクトです。 ChapterInfo オブジェクトは、チャプターの開始してもブックに改ページ調整されるように作成されたリストの PageInfo オブジェクトは保持されます、段落のインデックスを示します。

PageInfo クラスを示しています図 3。 これは、ページ、段落のインデックスと、その段落内の文字のインデックスで始まるしも、リストの WordInfo オブジェクトは保持されますを示します。 WordInfo クラスを示しています図 4。 このクラスは段落部分としてページと、テキストの単語の単語の座標位置を示すため WordInfo オブジェクトごとに 1 つの単語に対応しています。

図 3 、PageInfo のクラスを表す各ページのページ

public class PageInfo
{
  public PageInfo()
  {
    this.Words = new List<WordInfo>();
  }

  public int ParagraphIndex { set; get; }

  public int CharacterIndex { set; get; }

  public bool IsLastPageInChapter { set; get; }

  public bool IsPaginated { set; get; }

  public int AccumulatedCharacterCount { set; get; }

  [XmlIgnore]
  public List<WordInfo> Words { set; get; }
}

図 4 WordInfo クラスは 1 つの単語を表す

public class WordInfo
{
  public int LocationLeft { set; get; }

  public int LocationTop { set; get; }

  public int ParagraphIndex { set; get; }

  public int CharacterIndex { set; get; }

  public int CharacterCount { set; get; }
}

Words プロパティ XmlIgnore、クラスの残りの部分にこのプロパティがシリアル化されないことを意味するとフラグが設定されてし、分離ストレージ、改ページ調整の情報の残りの部分と一緒には保存されませんので、PageInfo クラスのことがわかります。 いくつかのほとんどの計算のこの決定が納得されます:「Phineas Finn」は、200,000 台を超える単語長さと WordInfo が 20 バイトのデータ メモリに、WordInfo オブジェクトはすべて 4 mb を超えるメモリを占有します. 悪くはないが 200,000 WordInfo オブジェクトを XML にシリアル化に変換を検討してください。 ページの先頭がわかっている場合は、パフォーマンス問題なく WordInfo オブジェクトが再作成されるため、FontMetrics クラスを使用して、ページ上の位置を計算する、非常に高速です。

図 5 BuildPageElement メソッドでは、Canvas に多数の TextBlock 要素が含まれている基本的には、PageInfo オブジェクトを変換する PageProvider が表示されます。 これは、実際には、画面上に描画このキャンバスです。

図 5 PageProvider BuildPageElement メソッド

FrameworkElement BuildPageElement(ChapterInfo chapter, PageInfo pageInfo)
{
  if (pageInfo.Words.Count == 0)
  {
    Paginate(chapter, pageInfo);
  }

  Canvas canvas = new Canvas();

  foreach (WordInfo word in pageInfo.Words)
  {
    TextBlock txtblk = new TextBlock
    {
      FontFamily = fontMetrics.Font.FontFamily,
      FontSize = this.fontSize,
      Text = paragraphs[word.ParagraphIndex].Text.
Substring(word.CharacterIndex, 
        word.CharacterCount),
        Tag = word
    };

    Canvas.SetLeft(txtblk, word.LocationLeft);
    Canvas.SetTop(txtblk, word.LocationTop);
    canvas.Children.Add(txtblk);
  }
  return canvas;
}

改ページ調整およびレイアウトの実際のコードは、UI が変更されません。 ページを作成する BuildPageElement メソッドは、UI オブジェクトを作成します。 改ページのコンポジションからの分離は、電子ブック リーダーのこのバージョンで新しいですし、レイアウトと改ページをバック グラウンド スレッドでする可能性があることを意味します。 このプログラムでは、実行していないがある点に注意してください。

パフォーマンスを得るだけでなく

I 最初 TextBlock レイアウト パフォーマンス上の理由で破棄を決定しました。 各単語を個別の TextBlock 要素を使用して 2 つ以上の多くの説得力のある理由があります。

段落を両端揃えにする場合は、まず、最初の基本的な手順です。 Silverlight の Windows Phone 7 の TextAlignment.Justify 列挙体のメンバーがサポートされていません。 独立したテキスト ブロックのすべての単語の場合は、理由を個々 の単語の間に余分なスペースを分配するだけです。

2 つ目の理由にはテキストを選択すると、問題が含まれます。 目的のテキストを選択できるようにすることができます: おそらくメモや注釈を文書に追加するのには、辞書または Bing や Wikipedia、語句を検索またはテキストをクリップボードにコピーするだけです. ユーザーがテキストを選択し、別の色で選択されているテキストを表示するのにはいくつかの方法を提供する必要があります。

1 つの TextBlock テキストの異なる部分違う色で表示できますか。 はい、Inlines プロパティと実行の別のオブジェクトが選択されているテキストのことです。 これは面倒ですが可能です。

困難な問題はまずテキストを選択して、ユーザーに知らせることです。 ユーザーはクリックするか、特定の単語をタッチして、複数の単語を選択するドラッグする必要があります。 しかし、段落全体で 1 つの TextBlock 要素が表示される場合は、どのような word がわかっているの操作を行いますでしょうか。 実行の個別のオブジェクトではなく、TextBlock はヒット テストを実行できます。

各単語は TextBlock の場合は、ヒット テスト ジョブがはるかに簡単になります。 もちろん、その他の課題に電話が発生します。 指が太く短い描線の選択を開始する前に、テキストを拡大するのには、ユーザーに必要な可能性があることを意味する小さなテキストを選択してください。

通常どおり、各プログラムの新機能が導入されるように、さらに多くの機能を示します。

Charles Petzold 長くを使用しての貢献エディターです MSDN Magazine*.*彼の新しい本、"プログラミング Windows Phone 7"(マイクロソフト プレス、2010年、) は、無料ダウンロードで利用可能な bit.ly/cpebookpdf

この記事を確認するのには次の技術のエキスパートに感謝: Chipalo Street