伝説のツール「NotesPeek」をQtでリメイク開発記2017-7-23

さて、ようやくリッチテキストにメスを入れる時が来ました。The time has come!

リッチテキストは、まぎれもなくアイテム(フィールド)なので、テキストフィールドや数値フィールドと同じように、アイテム型(WORD)を先頭に持つバイナリデータです。

アイテム型
TYPE_TEXT テキストフィールドデータ
TYPE_NUMBER 数値フィールドデータ
TYPE_TIME 日時フィールドデータ
TYPE_COMPOSITE リッチテキストフィールドデータ

なので、肝心なのは値の並べ方がどのようになっているかということになります。

リッチテキストは、書式付きの文字列や図、添付ファイルのようなデータが混在します。そのため、その複合データを混在させるために、データの種類と長さを表す「シグネチャ」と各データ固有の情報とを一組としたデータの羅列で構成されます。

例えば、リッチテキストに、何の変哲もない文字列を1行だけ入れたとします。これをNSFinderで覗いています。

f:id:takahide-kondoh:20170723191543p:plain

NSFinderで「Body」フィールドを見てみると、以下のように「(CD)PABDEFINITION」、「(CD)PABREFERENCE」、「(CD)TEXT」の3つで構成されているのがわかります。

f:id:takahide-kondoh:20170723191554p:plain

CDPABDEFINITIONは、段落の定義になります。

typedef struct {
   WSIG Header;        /* Used to quickly recognize structure type */
   WORD PABID;         /* ID of this PAB */
   WORD JustifyMode;   /* paragraph justification type */
   WORD LineSpacing;   /* (2*(Line Spacing-1)) (0:1,1:1.5,2:2,etc) */
   WORD ParagraphSpacingBefore; /* # LineSpacing units above para */
   WORD ParagraphSpacingAfter;  /* # LineSpacing units below para */
   WORD LeftMargin;    /* leftmost margin, twips rel to abs left */
                       /* (16 bits = about 44") */
   WORD RightMargin;   /* rightmost margin, twips rel to abs right */
                       /* (16 bits = about 44") */
                       /* Special value "0" means right margin */
                       /* will be placed 1" from right edge of */
                       /* paper, regardless of paper size. */
   WORD FirstLineLeftMargin; /* leftmost margin on first line */
                       /* (16 bits = about 44") */
   WORD Tabs;          /* number of tab stops in table */
   SWORD Tab[MAXTABS]; /* table of tab stop positions, negative */
                       /* value means decimal tab */
                       /* (15 bits = about 22") */
   WORD Flags;         /* paragraph attribute flags - PABFLAG_xxx */
   DWORD TabTypes;     /* 2 bits per tab */
   WORD Flags2;        /* extra paragraph attribute flags - PABFLAG2_xxx */
} CDPABDEFINITION;

先頭にある「WSIG Header」がシグネチャです。PABIDは段落定義のIDで、PABREFERENCEから参照されます。JustifyModeは行揃えの定義です。その他にマージンやタブの定義も見て取れます。

CDPABREFERENCEは、段落定義を参照します。

typedef struct {
   BSIG Header;
   WORD PABID; /* ID number of the CDPABDEFINITION */
               /* used by this paragraph */
} CDPABREFERENCE;

参照だけなので、シグネチャ部分を除けばPABIDのみのシンプルな構造です。

CDTEXTは、文字列データです。

typedef struct {
   WSIG   Header; /* Tag and length */
   FONTID FontID; /* Font ID */
/* The 8-bit text string follows... */
} CDTEXT;

typedef struct {
#ifdef LITTLE_ENDIAN_ORDER
    BYTE Face;      /* Font face (FONT_FACE_xxx) */
    BYTE Attrib;        /* Attributes (ISBOLD,etc) */
    BYTE Color;     /* Color index (NOTES_COLOR_xxx) */
    BYTE PointSize; /* Size of font in points */
#else
    BYTE PointSize; /* Size of font in points */
    BYTE Color;     /* Color index (NOTES_COLOR_xxx) */
    BYTE Attrib;        /* Attributes (ISBOLD,etc) */
    BYTE Face;      /* Font face (FONT_FACE_xxx) */
#endif
} FONTIDFIELDS;

FONTID自体は単なるDWORD型ですが、実はFONTIDFIELDSという構造体でもあります。ご覧の通り、フォントの書式設定が含まれています。

CDTEXTのデータだけでは、文字列本体は取得できません。CDTEXTに続いて文字列データがLMBCS形式で存在します。Headerデータには値の全体の長さが含まれるので、その全体の長さからCDTEXT構造体のサイズ分引けば、残りが文字列の長さとなります。

各CD〜の先頭には「BSIG Header」や「WSIG Header」がありましたが、そのほかに「LSIG Header」が存在します。これらはデータの長さに応じて「Byte長シグネチャ」「Word長シグネチャ」「Long長シグネチャ」を表しています。前述のCDPABREFERENCEのような参照IDのみのような短いもの、テキストのような一般的なもの、画像のような巨大なもの、それぞれに合わせて使い分けられています。

Notesのアイテム型もそこそこ種類がありますが、リッチテキストの中身を構成するコンポジットデータの種類はその数をしのぐでしょう。ただし、このリッチテキストアイテムを制することができれば、他の書式(HTMLやWord、その他リッチテキスト書式など)との相互変換をする場合に必要になるので、対応できるコンポジットデータの数を増やしておくことは、非常に大きな武器になることは間違いないでしょう。