Почему CGContextShowGlyphsAtPositions() не работает, когда работает CGContextShowGlyphsAtPoint()?

Я написал простое приложение Cocoa для Mac OS X (10.7), используя Xcode 4.2. Все, что делает приложение, — это создает окно с прокручиваемым массивом вложенных представлений, каждое из которых представляет страницу для рисования на очень низком уровне. Метод isFlipped вложенного представления возвращает YES, поэтому источником каждого вложенного представления является верхний левый угол. Используя различные подпрограммы Core Graphics, я могу успешно рисовать линии и заполнять контуры, а также все эти забавные вещи PostScript.

Меня смущает рисование глифов из заданного шрифта.

Вот полный код, вырезанный и вставленный из программы, для метода -drawRect: sub-View -

- (void)drawRect:(NSRect)dirtyRect
{
  // Start with background color for any part of this view
  [[NSColor whiteColor] set];
  NSRectFill( dirtyRect );

  // Drop down to Core Graphics world, ensuring there's no side-effects
  context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
  CGContextSaveGState(context);
  {
    //CGFontRef theFont = CGFontCreateWithFontName(CFSTR("American Typewriter"));
    //CGContextSetFont(context, theFont);
    CGContextSelectFont(context, "American Typewriter", 200, kCGEncodingMacRoman);
    CGContextSetFontSize(context, 200);

    // Adjust the text transform so the text doesn't draw upside down
    CGContextSetTextMatrix(context, CGAffineTransformScale(CGAffineTransformIdentity, 1, -1));

    CGContextSetTextDrawingMode(context, kCGTextFillStroke);
    CGContextSetRGBFillColor(context, 0.0, .3, 0.8, 1.0);

    // Find the center of view's (not dirtyRect's) bounds
    // View is 612 x 792 (nominally 8.5" by 11")

    CGPoint centerPoint;
    CGRect bds = [self bounds];
    centerPoint.x = bds.origin.x + bds.size.width  / 2;
    centerPoint.y = bds.origin.y + bds.size.height / 2;

    // Create arrays to hold glyph IDs and the positions at which to draw them.
    #define glyphCount 1                        // For now, just one glyph
    CGGlyph glyphs[glyphCount];
    CGPoint positions[glyphCount];

    glyphs[0] = 40;                             // Glyph ID for '@' character in above font
    positions[0] = centerPoint;

    // Draw above center.  This works.
    CGContextShowGlyphsAtPoint(context, centerPoint.x, centerPoint.y - 200.0, glyphs, glyphCount);

    // Draw at center.  This works.
    CGContextShowGlyphsAtPoint(context, positions[0].x, positions[0].y, glyphs, glyphCount);

    // Draw below center.  This fails (draws nothing).  Why?
    positions[0].y += 200.0;
    CGContextShowGlyphsAtPositions(context, glyphs, positions, glyphCount);
  }
  CGContextRestoreGState(context);
}

Что заставило меня рвать на себе волосы, так это то, что первые два вызова рисования глифов с использованием CGContextShowGlyphsAtPoint() работают нормально, как и ожидалось, но третья попытка с использованием CGContextShowGlyphsAtPositions() никогда ничего не рисует. Таким образом, на странице только два символа @, а не три. Эта разница в поведении не зависит от того, использовал ли я ранее CGContextSetFont() или CGContextSelectFont().

Должно быть какое-то скрытое изменение в состоянии, или что-то совершенно другое под капотом с этими двумя почти идентичными процедурами рисования глифов Core Graphics, но все мои эксперименты до сих пор не продемонстрировали, что это может быть.

Вздох. Я просто хочу эффективно рисовать массив глифов в соответствующем массиве позиций в представлении.

Есть идеи, что я ошибаюсь?


person jsbox    schedule 13.06.2013    source источник


Ответы (2)


После долгих экспериментов, вызванных тем, что ответ Питера Хози ударил меня по голове (даже несмотря на то, что некоторые из них не совсем верны, большое спасибо!), вот источник моего замешательства и объяснение, в котором я почти уверен, что оно правильное (ну, в любом случае код делает то, что я от него ожидаю).

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

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

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

В коде, опубликованном выше, данные о положении, которые CGContextShowGlyphsAtPositions() использует для рисования коллекции глифов, все относительны к точке пользовательского пространства, соответствующей исходной точке текущей текстовой матрицы, которая была переведена вправо от предыдущей нарисованный символ '@'. Поскольку я использовал такой большой размер шрифта, position[0] заставлял следующий глиф '@' отрисовываться за пределами представления, поэтому он не был виден, но рисовался.

Но есть еще некоторые нюансы между двумя процедурами. CGContextShowGlyphsAtPositions() никогда нельзя использовать для размещения глифов в любой абсолютной позиции пользовательского пространства. Так как же сказать ему, с чего начать? Ответ (или по крайней мере один ответ) состоит в том, что CGContextShowGlyphsAtPoint() обновляет источник текстовой матрицы до заданной точки пользовательского пространства, даже если нет глифов для рисования. И CGContextShowGlyphsAtPoint() должен переводить текстовую матрицу после каждого отрисовываемого им глифа, потому что какой смысл (так сказать) рисовать всю коллекцию глифов друг над другом.

Таким образом, можно «переместиться» в точку, не являющуюся путем, в пользовательском пространстве, используя CGContextShowGlyphsAtPoint() со счетчиком глифов, равным 0, а затем можно вызвать CGContextShowGlyphsAtPositions() (любое количество раз) с вектором позиций, каждая из которых будет обрабатываться относительно исходная точка текстовой матрицы (или, на самом деле, соответствующая ей точка пользовательского пространства) без обновления исходной текстовой матрицы вообще при возврате CGContextShowGlyphsAtPositions().

Наконец, обратите внимание, что данные о местоположении, предоставленные CGContextShowGlyphsAtPositions(), представлены в координатах пользовательского пространства. Комментарий в заголовочном файле Apple для этих подпрограмм прямо говорит об этом.

person jsbox    schedule 16.06.2013

Одна из возможностей такова, из документа CGContextShowGlyphsAtPositions:

Положение каждого глифа указывается в текстовом пространстве и, как следствие, трансформируется через текстовую матрицу в пространство пользователя.

текстовая матрица — это отдельное свойство контекста, отличное от матрицы текущего преобразования графического состояния.

Это не говорит о CGContextShowGlyphsAtPoint:

Эта функция отображает массив глифов в указанной позиции в пространстве пользователя.

(Выделение добавлено к обеим цитатам.)

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

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

(Попробуйте установить масштаб на 0,5 вместо -1, и вы поймете, что я имею в виду.)

Я рекомендую снять ваш CGContextSetTextMatrix звонок.

person Peter Hosey    schedule 14.06.2013
comment
Спасибо. Ваше предложение покопаться в текстовой матрице, чтобы посмотреть, что произойдет, было как раз тем, что мне было нужно, чтобы выбраться из ямы замешательства. Но ваше предложение удалить вызов CGContextSetTextMatrix не является хорошим, как я объясню ниже. - person jsbox; 16.06.2013
comment
А заголовочный файл для этого метода говорит об обратном, для ЛОЛ. Спасибо, Apple: позиции указаны в пользовательском пространстве - person Adam; 08.12.2013