[Kde-games-devel] Colour fonts

Thomas Friedrichsmeier kde-games-devel@mail.kde.org
Mon, 20 Jan 2003 15:53:10 +0100


--------------Boundary-00=_MCP0Z1TOFTF4JU23CSVY
Content-Type: text/plain;
  charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

> This is exactly what I'm after, too. What does your implementation look
> like, rougly?

I'm attaching the code for the function in question. It contains quite so=
me=20
stuff specific to Taxipilot, but I hope it's still mostly readable. Rough=
ly,=20
what it does is to try how large the string would get in 24 point, then m=
ake=20
an educated guess, what point-size might be more appropriate, check how l=
arge=20
that would really get, and loop. I warned you, it's not very pretty and i=
t's=20
not efficient at all, but I couldn't figure out a better way. Font-handli=
ng=20
remains a mystery to me.
Since I only need this on the canvas, the function additionally puts the=20
resulting pixmap in a QCanvasPixmaparray so it can be directly used in a=20
sprite.

> > I've had to implement something like this for taxipilot, but my solut=
ion
> > is slow and awkward and does not always work as expected, so a nice a=
nd
> > clean function in libkdegames would be great.
>
> Good to hear you're still working on that game. I haven't been active o=
n
> this list for a year and I think it's soon time that I get my game to a=
t
> least a usable state. ;)

Well, I haven't found much time to work on it recently, so if you ever ge=
t=20
frustrated with your game, you're heartedly invited to join taxipilot ;-)

Thomas

--------------Boundary-00=_MCP0Z1TOFTF4JU23CSVY
Content-Type: text/x-c++src;
  charset="iso-8859-1";
  name="createTextPixmap.cpp"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="createTextPixmap.cpp"

/** Creates a single frame CachablePixmapArray (uncached) with the text text_p written in font font (given as it's name) and using color color.
Qt::TextFlags can be used for formatting. The text will be written in a font-size such that it will completely fit inside an area of
max_widht / height, trying to get as close to max_width / height as possible. If smooth is specified, only font-sizes that really are
available on the system (without scaling) will be used. Note that on some systems this is always the case, and the option might
not have any effect. clip and align_handle jointly specify where the hot spot of the returned frame will be. If align_handle is
given, the hot spot will be placed according to the text_flags-alignment (e.g. in the center of the image for center-align text). If align_handle
is not given, the top-left will always be used. In this case, if clip is false, the top-left of a hypothetic, unclipped image of max_width / height
will be used, whereas is clip is true, the natural top-left of the clipped image is used. Technically the image will always be clipped
to the dimensions actually needed in order to save resources, but with clip set to false, it will seem like it had not been clipped. */
CachablePixmapArray * Cdp::createTextPixmapArray (const QString & text_p, const QString & font, QColor color, int text_flags, int max_width, int max_height, bool smooth, bool clip, bool align_handle) {
        int pos;
        int point_size = 24;                            // size to start trying with
        int old_point_size;
        int use_width, use_height;
        QString text = text_p;                                  // unfortunately, we have to copy for now.

        if (text == "") {               // empty text may crash (due to zero size)
                text = " ";
        }

        // create font, and a QRect for calculating size to use
        QFont fnt (font);
        fnt.setStyleStrategy (static_cast<QFont::StyleStrategy> (QFont::PreferDefault | QFont::PreferMatch));
        QRect brect;

        // convert literal \n's in text to newlines
        while ((pos = text.find ("\\n")) >= 0) {
                text.replace (pos, 2, "\n");
        }

        // create a Pixmap and Painter
        QPixmap text_pm (max_width, max_height);
        text_pm.fill (Qt::black);
        QPainter painter (&text_pm);

        // determine optimal point-size
        float factor_x, factor_y, factor;
        int too_large = 37;                     // needed to make sure, the below algorithm always comes to an end.
                                                                // (and fonts don't get unreasonably large)

        pos = 0;
        do {
                fnt.setPointSizeFloat (old_point_size = point_size);
                
                // calculate the size, text would have like this
                // (note, that since we want an honest answer, we must not yet constrain drawing to max_width/height)
                painter.setFont (fnt);
                brect = painter.boundingRect (0, 0, max_width, max_height, text_flags, text);

                // from this, make a good guess which point-size to try next
                factor_x = static_cast<float> (max_width) / brect.width ();
                factor_y = static_cast<float> (max_height) / brect.height ();

                if (factor_x < factor_y) {
                        factor = factor_x;
                } else {
                        factor = factor_y;
                }
                if (factor < 1.0) {
                        too_large = point_size;         // if scaling down, the previous size was too large
                }
                point_size = static_cast<int> (point_size * factor);
                if (factor >= 1.0) {                                    // if scaling up, make sure we don't scale up beyond what we already know, does not fit ...
                        if (point_size >= too_large) {
                                --too_large;                          // ... and set the limit a bit lower for the next iteration
                                point_size = (2*too_large + old_point_size) / 3;
                        }
                }
/*              QString dummy;
                debug_msg ("Pointsize: " + dummy.setNum (old_point_size) + " Factor: " + dummy.setNum (factor) + "Width : " + dummy.setNum (brect.width ()), Warn); */
                pos++;                                        // temporary for preventing infinite loops (as long as those might still occur)
        } while (((point_size != old_point_size) && (pos < 50)));               // while point-size not yet optimal, iterate again

        if (pos >= 49) {
                debug_msg ("Coding error! Infinite loop caught during calculation of font-sizes. Please contact the author of the game.", Warn);
        }

        if (!point_size) {                      // just in case ...
                point_size = 1;
        }

        if (smooth) {                                   // get next smaller font-size that can be displayed smoothly
                int i = 0;
                do {
                        fnt.setPointSize(point_size - i);
                        i++;
                } while ( (((point_size - i) / (float) point_size) > 0.5) && (!QFontInfo::QFontInfo(fnt).exactMatch()));                // don't go below half the optimal point size
                if ( ((point_size - i) / (float) point_size) >= 0.5) {          // if we found something reasonable, use that
                        point_size -= i;
                }
        }

        fnt.setPointSize(point_size);                     // calculate size once again ...
        painter.setFont (fnt);
        brect = painter.boundingRect (0, 0, max_width, max_height, text_flags, text);

        use_width = brect.width ();                     // now clip (we always clip, but if not requested return an offset as though we had not (somewhere below))
        use_height = brect.height ();
        painter.end ();                                                                 // can't resize while painting
        text_pm.resize (use_width, use_height);
        painter.begin (&text_pm);
        painter.setFont (fnt);

        painter.setPen(color);                  // finally, draw the text
        painter.drawText(0, 0, use_width, use_height, text_flags, text);
        painter.end ();

        // create a mask
        QBitmap mask (use_width, use_height);
        mask.fill (Qt::color0);
        painter.begin (&mask);
        painter.setFont (fnt);
        painter.setPen(Qt::color1);
        painter.drawText(0, 0, use_width, use_height, text_flags, text);
        painter.end ();

        text_pm.setMask (mask);

        if (align_handle) {
                if (text_flags & Qt::AlignLeft) {                       // misusing use_width, use_height here!
                        use_width = 0;
                } else if (text_flags & Qt::AlignHCenter) {
                        use_width /= 2;
                }

                if (text_flags & Qt::AlignTop) {
                        use_height = 0;
                } else if (text_flags & Qt::AlignVCenter) {
                        use_height /= 2;
                }
        } else {
                if (clip) {
                        use_width = use_height = 0;
                } else {
                        use_width = -brect.left ();
                        use_height = -brect.top ();
                }
        }

        CachablePixmapArray * ret_pma = new CachablePixmapArray ();
        ret_pma->setImage (0, new QCanvasPixmap (text_pm, QPoint (use_width, use_height)));

        return (ret_pma);
}

--------------Boundary-00=_MCP0Z1TOFTF4JU23CSVY--