高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先

GX 05 - QuickDraw GX による EPS のサポート

(95 年 5 月 1 日): 更新

Q: QuickDraw GX を使って EPS ファイルをサポートするための正しい方法を教えてください。
QuickDraw プレビューを GX シェイプに変換し、PostScript コードを含むタグをアタッチすればいいと思うのですが、その結果、印刷を行うと、PostScript イメージの天地が逆になってしまいます。これは、GX と PostScript で印刷の向きが逆になっていることが原因だと思います。この問題を解決するために、PostScript のコードを修正する必要はありますか。

A: PostScript のシノニムを使うときには必要です。特に、QD の座標空間を考慮に入れておく必要があります。QD では、0,0 をページの左上隅と定義していますが、PostScript では、0,0 を左下隅の座標と定義しています。このことについては、「Quickdraw GX Printing」マニュアルの 4〜13 ページに詳しい解説が書かれています。

この問題を扱うには、多数の方法があります。その 1 つに、translatescale を使って、座標系を切り替えるという方法があります。たとえば、ページの縦が 760 ピクセルの場合は、次のコードを使って、0,0 をページの左下隅に変換できます。

0 760 translate
1 -1 scale
newpath
こうすれば、残りの PostScript コードは、そのまま送信できます。ただし、GX オブジェクトを描画する前には、translate および scale 演算の効果を元に戻すことを忘れないでください。そうしないと、GX オブジェクトの天地は逆になってしまいます。

Q: ご指摘の方法は、ユーザに EPS のエクスポートをさせないことを前提にすれば、たしかに多くのアプリケーションを対象に QuickDraw GX で EPS をサポートする方法として申し分ありません。しかし、EPS を位置に依存しないシェイプ (単調化されたピクチャ・シェイプの一部として) としてサポートする必要があるのです。次に示す方法は、ページの高さがわかっていることを前提にしているため、アプリケーション間 (たとえば、'qdgx' クリップボードといくつかのファイル形式の間) でデータ交換を行うときにはうまく動作しません。

EPS プレビューをピクチャ・シェイプの内部に封入しました。EPS シノニム・タグはピクチャ・シェイプにアタッチし、縦方向を反転するマッピングは、プレビュー・シェイプとピクチャ・シェイプそのものの両方に適用しました。PostScript は 1 度縦方向の反転を行い (これで正しく印刷できる)、封入したプレビュー・シェイプは 2 度と縦方向の反転を行えばいいだろう (これで正しく画面に表示できる)、と考えたためです。しかしその結果、プレビュー・シェイプは正しく画面に表示されたものの、PostScript は正しい場所に印刷されませんでした。位置に依存しない方法で、この問題をうまく処理できる方法を教えてください。

A: Apple では、EPStoShape という関数を提供しています。この関数は、EPS を GX シェイプにエンキャプシュレートする方法の 1 つの実例となっています。ここで説明するサンプルは非常に簡単なものなので、実際のアプリケーションで使用するときには、適切な拡張を行う必要があります。

サンプル・コードが扱うことのできないケースが 2 つあります。

1. まず、サイズの大きな EPS ファイルの場合です。EPStoShape ルーチンでは、EPS ファイル全体を 1 つのハンドルの中に読み込みます。そして、このハンドルが、'post' コレクションとしてシェイプに追加されます。このルーチンは、サイズの小さなサンプル・ファイルでは正常に動作しますが、サイズの大きな EPS ファイルをサポートするためには、このルーチンを修正して、ファイルを複数のセクションに分けて読み込み、複数の小さなコレクションをシェイプに追加できるようにする必要があります。これにより、印刷時に必要なメモリ容量が少なくなります。
2. EPStoShape 関数は、EPS ファイルの中で %%BoundingBox コメントを検索し、書類/シェイプのサイズを判定します。このコメントは、通常、実際の書類のデータの直前に置かれており、境界ボックスの上端、左端、下端、右端を表す 4 つの整数がその後に続きます。ただし、EPS ファイルの書き出しをより容易にするため、このコメントの書式は拡張されています。現在では、次のように書くことができます。
%% BoundingBox: (atend)
< タ際の書類のデータ>;
%% BoundingBox: 0 0 10 10
ここで、'(atend)' は、実際の境界ボックスが書類データの末尾にあることをリーダに指示します。EPStoShape 関数では、最初の BoundingBox コメントに 4 つの座標が含まれていることを前提にしており、座標の代わりに (atend) キーワードが検出されると、意味のない PostScript コードを生成してしまいます。このルーチンを実際のアプリケーションで使うには、(atend) コンストラクトをチェックし、それが検出された場合は 2 番目の BoundingBox コメントを検索するように、ルーチンを修正してください。


これら 2 つの修正は、比較的簡単にインプリメントできるはずです。

ルーチン: EPSFileToShape

EPS ファイルを読み込み、その外部にシェイプを作成します。その中に PICT リソースが含まれている場合、それは Skia に変換されます。また、PICT リソースが含まれていない場合、四角形のシェイプは、0,0 を起点として、EPS の BoundingBox:: コメントから得られた境界ボックスの幅と高さになります。

いずれのケースでも、EPS ファイルは、'post' タグ・シノニムとともにシェイプにアタッチされます。

このようにして追加の PostScript がアタッチされ、Skia-PostScript Imaging System を介して印刷を行うときにも、PostScript がシェイプの変形を経由して適切にレンダリングを行えるようになります (もちろん、ここで詳しいことを説明するわけにはいきませんが)。

このためのアルゴリズムは、『PostScript Language Reference Manual』(2nd edition) の 724 ページに記載されています。

ステップ 1.
left bottom translate %% ここで、left、bottom は、QD シェイプの境界の leftbottom
ステップ 2.
1 -1 scale %% Skia から PostScript 空間に戻る
ステップ 3.
-x1 -y1 translate %% 原点を EPS 境界ボックスの左下隅に置く
このルーチンでは、EPS データやバージョンの有効性をチェックしません。PostScript が境界ボックスのコメントを含む有効な EPS であることを前提にしています。

/******************************************************/

gxShape EPSFileToShape(Str255 fName, short vRefNum)
{
    OSErr			status;
    PicHandle			thePict;
    Rect			theRect;
    Point			patStretch = {1,1};
    gxShape			theShape = nil;
    short			refNum;
    gxTag			synTag;
    long			size;
    Handle			hPsData;
    long			startComment, endComment;
    Str32			num1;
    char			tagString[300];
    long			tagSize, pieceSize;

    gxTranslationStatistic	seanStats;

    /** PostScript データを取得 **/

    status = FSOpen(fName, vRefNum, &refNum);
    ncheck(status);

    GetEOF(refNum, &size);
    hPsData = NewHandle(size);
    HLock(hPsData);


    /** EPS ファイル全体を読み込む **/
    status = FSRead(refNum, &size, *hPsData);
    ncheck(status);

    FSClose(refNum);

    /** 境界ボックスのコメントを検索 (数値のみ) **/

    startComment = Munger(hPsData, 0, "%%BoundingBox:", 14, nil, 0) + 14;
    endComment = Munger(hPsData , startComment, "\n", 1, nil, 0);

    refNum = OpenRFPerm(fName, vRefNum, 0);

    thePict = (PicHandle)GetResource('PICT', 256);
    if (thePict != nil) {

            theRect = (*thePict)->picFrame;

            theShape = GXNewShape(gxPictureType);

            (void) GXConvertPICTToShape(thePict, gxDefaultOptionsTranslation,
					&theRect, &theRect, patStretch,
					theShape, &seanStats);

            status = GXGetGraphicsError(nil);

            ncheck(status);

            ReleaseResource((Handle)thePict);

    } else {

            float            x1, y1, x2, y2;
            gxRectangle      theRectangle;

            sscanf(*hPsData + startComment, "%f %f %f %f", &x1, &y1, &x2, &y2);
            theRectangle.top = 0;
            theRectangle.left = 0;
            theRectangle.right = X2Fix(x2 - x1);
            theRectangle.bottom = X2Fix(y2 - y1);

            /** 埋め込み PostScript の原点変換のために 
            		QuickDraw の四角形が必要 **/

            theRect.top = 0;
            theRect.left = 0;
            theRect.bottom= theRectangle.bottom >> 16;
            theRect.right = theRectangle.right >> 16;

            theShape = GXNewRectangle(&theRectangle);

    }//end if

    CloseResFile(refNum);


    /** QuickDraw 四角形の Left Bottom に変換 **/

    tagSize = 0;
    NumToString(theRect.left, num1);
    num1[(unsigned char)num1[0] + 1] = ' ';
    pieceSize = (unsigned char)num1[0] + 1;
    BlockMove(&(num1[1]), tagString, pieceSize);
    tagSize += pieceSize;

    NumToString(theRect.bottom, num1);
    pieceSize = (unsigned char)num1[0];
    BlockMove(&(num1[1]), tagString + tagSize, pieceSize);
    tagSize += pieceSize;

    BlockMove(" translate\n", tagString + tagSize, 11);
    tagSize += 11;

    /** 座標軸を反転 **/
    BlockMove(" 1 -1 scale\n", tagString + tagSize, 12);
    tagSize += 12;

    synTag = GXNewTag( gxPostScriptTag, tagSize, tagString);
    GXSetShapeTags(theShape, gxPostScriptTag, 0, 0, 1, &synTag);
    GXDisposeTag(synTag);

    /**  -(LLx) -(LLy) に変換 **/

    // オペランド・スタック上のオペランド・コメント
    文字列から得た 4 つのボックス番号
    synTag = GXNewTag( gxPostScriptTag, endComment - startComment + 1, 
    			*hPsData + startComment);
    GXSetShapeTags(theShape, gxPostScriptTag, 0, 0, 1, &synTag);
    GXDisposeTag(synTag);

    // 後の 2 つをポップし、最初の 2 つを負の数に変換
    synTag  = GXNewTag( gxPostScriptTag, 36, 
    			"pop pop neg exch neg exch translate\n");
    GXSetShapeTags(theShape, gxPostScriptTag, 0, 0, 1, &synTag);
    GXDisposeTag(synTag);


    /** PostScript シノニム本体 **/

    synTag = GXNewTag( gxPostScriptTag, size, *hPsData);
    GXSetShapeTags(theShape, gxPostScriptTag, 0, 0, 1, &synTag);
    GXDisposeTag(synTag);

    DisposHandle(hPsData);

    check(theShape);
    return(theShape);

}//EPSFileToShape


[ Technical Q&A's : QuickDraw GX : GX Graphics : GX05 ]