Back to index

lightning-sunbird  0.9+nobinonly
nsFontMetricsPango.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:expandtab:shiftwidth=4:tabstop=4:
00003  */
00004 /* ***** BEGIN LICENSE BLOCK *****
00005  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00006  *
00007  * The contents of this file are subject to the Mozilla Public License Version
00008  * 1.1 (the "License"); you may not use this file except in compliance with
00009  * the License. You may obtain a copy of the License at
00010  * http://www.mozilla.org/MPL/
00011  *
00012  * Software distributed under the License is distributed on an "AS IS" basis,
00013  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00014  * for the specific language governing rights and limitations under the
00015  * License.
00016  *
00017  * The Original Code is mozilla.org code.
00018  *
00019  * The Initial Developer of the Original Code is Christopher Blizzard
00020  * <blizzard@mozilla.org>.  Portions created by the Initial Developer
00021  * are Copyright (C) 2004 the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include <strings.h>
00040 #include "nsFont.h"
00041 #include "nsIDeviceContext.h"
00042 #include "nsICharsetConverterManager.h"
00043 #include "nsIPref.h"
00044 #include "nsServiceManagerUtils.h"
00045 
00046 #define PANGO_ENABLE_BACKEND
00047 #define PANGO_ENABLE_ENGINE
00048 
00049 #include "nsFontMetricsPango.h"
00050 #include "nsRenderingContextGTK.h"
00051 #include "nsDeviceContextGTK.h"
00052 #include "nsFontConfigUtils.h"
00053 
00054 #include "nsUnicharUtils.h"
00055 #include "nsQuickSort.h"
00056 #include "nsFontConfigUtils.h"
00057 
00058 #include <fontconfig/fontconfig.h>
00059 #include <gdk/gdk.h>
00060 #include <gdk/gdkx.h>
00061 #include <freetype/tttables.h>
00062 
00063 #include "mozilla-decoder.h"
00064 
00065 #define FORCE_PR_LOG
00066 #include "prlog.h"
00067 
00068 // Globals
00069 
00070 static PRLogModuleInfo            *gPangoFontLog;
00071 static int                         gNumInstances;
00072 
00073 // Defines
00074 
00075 // This is the scaling factor that we keep fonts limited to against
00076 // the display size.  If a pixel size is requested that is more than
00077 // this factor larger than the height of the display, it's clamped to
00078 // that value instead of the requested size.
00079 #define FONT_MAX_FONT_SCALE 2
00080 
00081 static NS_DEFINE_CID(kCharsetConverterManagerCID,
00082                      NS_ICHARSETCONVERTERMANAGER_CID);
00083 
00084 #ifdef DEBUG
00085 #define DUMP_PRUNICHAR(ustr, ulen) for (PRUint32 llen=0;llen<ulen;llen++) \
00086                                       printf("0x%x ", ustr[llen]); \
00087                                    printf("\n");
00088 #endif
00089 
00090 // rounding and truncation functions for a Freetype floating point number 
00091 // (FT26Dot6) stored in a 32bit integer with high 26 bits for the integer
00092 // part and low 6 bits for the fractional part. 
00093 #define MOZ_FT_ROUND(x) (((x) + 32) & ~63) // 63 = 2^6 - 1
00094 #define MOZ_FT_TRUNC(x) ((x) >> 6)
00095 #define CONVERT_DESIGN_UNITS_TO_PIXELS(v, s) \
00096         MOZ_FT_TRUNC(MOZ_FT_ROUND(FT_MulFix((v) , (s))))
00097 
00098 // Static function decls
00099 
00100 static PangoLanguage *GetPangoLanguage(nsIAtom *aLangGroup);
00101 
00102 static void   FreeGlobals    (void);
00103 
00104 static PangoStyle  CalculateStyle  (PRUint8 aStyle);
00105 static PangoWeight CalculateWeight (PRUint16 aWeight);
00106 
00107 static nsresult    EnumFontsPango   (nsIAtom* aLangGroup, const char* aGeneric,
00108                                      PRUint32* aCount, PRUnichar*** aResult);
00109 static int         CompareFontNames (const void* aArg1, const void* aArg2,
00110                                      void* aClosure);
00111 
00112 nsFontMetricsPango::nsFontMetricsPango()
00113 {
00114     if (!gPangoFontLog)
00115         gPangoFontLog = PR_NewLogModule("PangoFont");
00116 
00117     gNumInstances++;
00118 
00119     mPangoFontDesc = nsnull;
00120     mPangoContext = nsnull;
00121     mLTRPangoContext = nsnull;
00122     mRTLPangoContext = nsnull;
00123     mPangoAttrList = nsnull;
00124     mIsRTL = PR_FALSE;
00125     mPangoSpaceWidth = 0;
00126 
00127     static PRBool initialized = PR_FALSE;
00128     if (initialized)
00129         return;
00130 
00131     // Initialized the custom decoders
00132     if (!mozilla_decoders_init())
00133         initialized = PR_TRUE;
00134 }
00135 
00136 nsFontMetricsPango::~nsFontMetricsPango()
00137 {
00138     if (mDeviceContext)
00139         mDeviceContext->FontMetricsDeleted(this);
00140 
00141     if (mPangoFontDesc)
00142         pango_font_description_free(mPangoFontDesc);
00143 
00144     if (mLTRPangoContext)
00145         g_object_unref(mLTRPangoContext);
00146 
00147     if (mRTLPangoContext)
00148         g_object_unref(mRTLPangoContext);
00149 
00150     if (mPangoAttrList)
00151         pango_attr_list_unref(mPangoAttrList);
00152 
00153     // XXX clean up all the pango objects
00154 
00155     if (--gNumInstances == 0)
00156         FreeGlobals();
00157 }
00158 
00159 
00160 NS_IMPL_ISUPPORTS1(nsFontMetricsPango, nsIFontMetrics)
00161 
00162 // nsIFontMetrics impl
00163 
00164 NS_IMETHODIMP
00165 nsFontMetricsPango::Init(const nsFont& aFont, nsIAtom* aLangGroup,
00166                          nsIDeviceContext *aContext)
00167 {
00168     mFont = aFont;
00169     mLangGroup = aLangGroup;
00170 
00171     // Hang on to the device context
00172     mDeviceContext = aContext;
00173     
00174     mPointSize = NSTwipsToFloatPoints(mFont.size);
00175 
00176     // Make sure to clamp the pixel size to something reasonable so we
00177     // don't make the X server blow up.
00178     nscoord screenPixels = gdk_screen_height();
00179     mPointSize = PR_MIN((screenPixels - 1) * FONT_MAX_FONT_SCALE, mPointSize);
00180     mPointSize = PR_MIN(2000, mPointSize);
00181 
00182     // enumerate over the font names passed in
00183     mFont.EnumerateFamilies(nsFontMetricsPango::EnumFontCallback, this);
00184 
00185     nsCOMPtr<nsIPref> prefService;
00186     prefService = do_GetService(NS_PREF_CONTRACTID);
00187     if (!prefService)
00188         return NS_ERROR_FAILURE;
00189         
00190     nsXPIDLCString value;
00191     const char* langGroup;
00192     mLangGroup->GetUTF8String(&langGroup);
00193 
00194     // Set up the default font name if it's not set
00195     if (!mGenericFont) {
00196         nsCAutoString name("font.default.");
00197         name.Append(langGroup);
00198         prefService->CopyCharPref(name.get(), getter_Copies(value));
00199 
00200         if (value.get())
00201             mDefaultFont = value.get();
00202         else
00203             mDefaultFont = "serif";
00204         
00205         mGenericFont = &mDefaultFont;
00206     }
00207 
00208     // set up the minimum sizes for fonts
00209     if (mLangGroup) {
00210         nsCAutoString name("font.min-size.");
00211 
00212         if (mGenericFont->Equals("monospace"))
00213             name.Append("fixed");
00214         else
00215             name.Append("variable");
00216 
00217         name.Append(char('.'));
00218         name.Append(langGroup);
00219 
00220         PRInt32 minimumInt = 0;
00221         float minimum;
00222         nsresult res;
00223         res = prefService->GetIntPref(name.get(), &minimumInt);
00224         if (NS_FAILED(res))
00225             prefService->GetDefaultIntPref(name.get(), &minimumInt);
00226 
00227         if (minimumInt < 0)
00228             minimumInt = 0;
00229 
00230         minimum = minimumInt;
00231 
00232         // The minimum size is specified in pixels, not in points.
00233         // Convert the size from pixels to points.
00234         minimum = NSTwipsToFloatPoints(NSFloatPixelsToTwips(minimum, mDeviceContext->DevUnitsToAppUnits()));
00235         if (mPointSize < minimum)
00236             mPointSize = minimum;
00237     }
00238 
00239     // Make sure that the pixel size is at least greater than zero
00240     if (mPointSize < 1) {
00241 #ifdef DEBUG
00242         printf("*** Warning: nsFontMetricsPango created with point size %f\n",
00243                mPointSize);
00244 #endif
00245         mPointSize = 1;
00246     }
00247 
00248     nsresult rv = RealizeFont();
00249     if (NS_FAILED(rv))
00250         return rv;
00251 
00252     // Cache font metrics for the 'x' character
00253     return CacheFontMetrics();
00254 }
00255 
00256 nsresult
00257 nsFontMetricsPango::CacheFontMetrics(void)
00258 {
00259     // Get our scale factor
00260     float f;
00261     float val;
00262     f = mDeviceContext->DevUnitsToAppUnits();
00263 
00264     mPangoAttrList = pango_attr_list_new();
00265 
00266     GList *items = pango_itemize(mPangoContext,
00267                                  "a", 0, 1, mPangoAttrList, NULL);
00268 
00269     if (!items)
00270         return NS_ERROR_FAILURE;
00271 
00272     guint nitems = g_list_length(items);
00273     if (nitems != 1)
00274         return NS_ERROR_FAILURE;
00275 
00276     PangoItem *item = (PangoItem *)items->data;
00277     PangoFcFont  *fcfont = PANGO_FC_FONT(item->analysis.font);
00278     if (!fcfont)
00279         return NS_ERROR_FAILURE;
00280 
00281     // Get our font face
00282     FT_Face face;
00283     face = pango_fc_font_lock_face(fcfont);
00284     if (!face)
00285        return NS_ERROR_NOT_AVAILABLE;
00286        
00287     TT_OS2 *os2;
00288     os2 = (TT_OS2 *) FT_Get_Sfnt_Table(face, ft_sfnt_os2);
00289 
00290     // mEmHeight (size in pixels of EM height)
00291     int size;
00292     if (FcPatternGetInteger(fcfont->font_pattern, FC_PIXEL_SIZE, 0, &size) !=
00293         FcResultMatch) {
00294         size = 12;
00295     }
00296     mEmHeight = PR_MAX(1, nscoord(size * f));
00297 
00298     // mMaxAscent
00299     val = MOZ_FT_TRUNC(face->size->metrics.ascender);
00300     mMaxAscent = NSToIntRound(val * f);
00301 
00302     // mMaxDescent
00303     val = -MOZ_FT_TRUNC(face->size->metrics.descender);
00304     mMaxDescent = NSToIntRound(val * f);
00305 
00306     nscoord lineHeight = mMaxAscent + mMaxDescent;
00307 
00308     // mLeading (needs ascent and descent and EM height) 
00309     if (lineHeight > mEmHeight)
00310         mLeading = lineHeight - mEmHeight;
00311     else
00312         mLeading = 0;
00313 
00314     // mMaxHeight (needs ascent and descent)
00315     mMaxHeight = lineHeight;
00316 
00317     // mEmAscent (needs maxascent, EM height, ascent and descent)
00318     mEmAscent = nscoord(mMaxAscent * mEmHeight / lineHeight);
00319 
00320     // mEmDescent (needs EM height and EM ascent
00321     mEmDescent = mEmHeight - mEmAscent;
00322 
00323     // mMaxAdvance
00324     val = MOZ_FT_TRUNC(face->size->metrics.max_advance);
00325     mMaxAdvance = NSToIntRound(val * f);
00326     // X may screw up if we try to measure/draw more than 32767 pixels in
00327     // one operation.
00328     mMaxStringLength = (PRInt32)floor(32767.0/val);
00329     mMaxStringLength = PR_MAX(1, mMaxStringLength);
00330 
00331     // mPangoSpaceWidth
00332     PangoLayout *layout = pango_layout_new(mPangoContext);
00333     pango_layout_set_text(layout, " ", 1);
00334     int pswidth, psheight;
00335     pango_layout_get_size(layout, &pswidth, &psheight);
00336     mPangoSpaceWidth = pswidth;
00337     g_object_unref(layout);
00338 
00339     // mSpaceWidth (width of a space)
00340     nscoord tmpWidth;
00341     GetWidth(" ", 1, tmpWidth, NULL);
00342     mSpaceWidth = tmpWidth;
00343 
00344     // mAveCharWidth (width of an 'average' char)
00345     //    XftTextExtents16(GDK_DISPLAY(), xftFont, &xUnichar, 1, &extents);
00346     //rawWidth = extents.width;
00347     //mAveCharWidth = NSToCoordRound(rawWidth * f);
00348     GetWidth("x", 1, tmpWidth, NULL);
00349     mAveCharWidth = tmpWidth;
00350 
00351     // mXHeight (height of an 'x' character)
00352     if (pango_fc_font_has_char(fcfont, 'x')) {
00353         PangoRectangle rect;
00354         PangoGlyph glyph = pango_fc_font_get_glyph (fcfont, 'x');
00355         pango_font_get_glyph_extents (PANGO_FONT (fcfont), glyph, &rect, NULL);
00356         mXHeight = NSToIntRound(rect.height * f / PANGO_SCALE);
00357     }
00358     else {
00359         // 56% of ascent, best guess for non-true type or asian fonts
00360         mXHeight = nscoord(((float)mMaxAscent) * 0.56 * f);
00361     }
00362 
00363     // mUnderlineOffset (offset for underlines)
00364     val = CONVERT_DESIGN_UNITS_TO_PIXELS(face->underline_position,
00365                                          face->size->metrics.y_scale);
00366     if (val) {
00367         mUnderlineOffset = NSToIntRound(val * f);
00368     }
00369     else {
00370         mUnderlineOffset =
00371             -NSToIntRound(PR_MAX(1, floor(0.1 *
00372                 MOZ_FT_TRUNC(face->size->metrics.height) + 0.5)) * f);
00373     }
00374 
00375     // mUnderlineSize (thickness of an underline)
00376     val = CONVERT_DESIGN_UNITS_TO_PIXELS(face->underline_thickness,
00377                                          face->size->metrics.y_scale);
00378     if (val) {
00379         mUnderlineSize = nscoord(PR_MAX(f, NSToIntRound(val * f)));
00380     }
00381     else {
00382         mUnderlineSize =
00383             NSToIntRound(PR_MAX(1,
00384                floor(0.05 * MOZ_FT_TRUNC(face->size->metrics.height) + 0.5)) * f);
00385     }
00386 
00387     // mSuperscriptOffset
00388     if (os2 && os2->ySuperscriptYOffset) {
00389         val = CONVERT_DESIGN_UNITS_TO_PIXELS(os2->ySuperscriptYOffset,
00390                                              face->size->metrics.y_scale);
00391         mSuperscriptOffset = nscoord(PR_MAX(f, NSToIntRound(val * f)));
00392     }
00393     else {
00394         mSuperscriptOffset = mXHeight;
00395     }
00396 
00397     // mSubscriptOffset
00398     if (os2 && os2->ySubscriptYOffset) {
00399         val = CONVERT_DESIGN_UNITS_TO_PIXELS(os2->ySubscriptYOffset,
00400                                              face->size->metrics.y_scale);
00401         // some fonts have the incorrect sign. 
00402         val = (val < 0) ? -val : val;
00403         mSubscriptOffset = nscoord(PR_MAX(f, NSToIntRound(val * f)));
00404     }
00405     else {
00406         mSubscriptOffset = mXHeight;
00407     }
00408 
00409     // mStrikeoutOffset
00410     mStrikeoutOffset = NSToCoordRound(mXHeight / 2.0);
00411 
00412     // mStrikeoutSize
00413     mStrikeoutSize = mUnderlineSize;
00414 
00415     pango_fc_font_unlock_face(fcfont);
00416 
00417     /*
00418     printf("%i\n", mXHeight);
00419     printf("%i\n", mSuperscriptOffset);
00420     printf("%i\n", mSubscriptOffset);
00421     printf("%i\n", mStrikeoutOffset);
00422     printf("%i\n", mStrikeoutSize);
00423     printf("%i\n", mUnderlineOffset);
00424     printf("%i\n", mUnderlineSize);
00425     printf("%i\n", mMaxHeight);
00426     printf("%i\n", mLeading);
00427     printf("%i\n", mEmHeight);
00428     printf("%i\n", mEmAscent);
00429     printf("%i\n", mEmDescent);
00430     printf("%i\n", mMaxAscent);
00431     printf("%i\n", mMaxDescent);
00432     printf("%i\n", mMaxAdvance);
00433     printf("%i\n", mSpaceWidth);
00434     printf("%i\n", mAveCharWidth);
00435     */
00436 
00437     return NS_OK;
00438 }
00439 
00440 NS_IMETHODIMP
00441 nsFontMetricsPango::Destroy()
00442 {
00443     mDeviceContext = nsnull;
00444     return NS_OK;
00445 }
00446 
00447 NS_IMETHODIMP
00448 nsFontMetricsPango::GetLangGroup(nsIAtom** aLangGroup)
00449 {
00450     *aLangGroup = mLangGroup;
00451     NS_IF_ADDREF(*aLangGroup);
00452 
00453     return NS_OK;
00454 }
00455 
00456 NS_IMETHODIMP
00457 nsFontMetricsPango::GetFontHandle(nsFontHandle &aHandle)
00458 {
00459     return NS_ERROR_NOT_IMPLEMENTED;
00460 }
00461 
00462 // nsIFontMetricsPango impl
00463 
00464 nsresult
00465 nsFontMetricsPango::GetWidth(const char* aString, PRUint32 aLength,
00466                              nscoord& aWidth,
00467                              nsRenderingContextGTK *aContext)
00468 {
00469     PangoLayout *layout = pango_layout_new(mPangoContext);
00470 
00471     pango_layout_set_text(layout, aString, aLength);
00472 
00473     if (mPangoSpaceWidth)
00474         FixupSpaceWidths(layout, aString);
00475 
00476     int width, height;
00477 
00478     pango_layout_get_size(layout, &width, &height);
00479 
00480     g_object_unref(layout);
00481 
00482     float f;
00483     f = mDeviceContext->DevUnitsToAppUnits();
00484     aWidth = NSToCoordRound(width * f / PANGO_SCALE);
00485 
00486     //    printf("GetWidth (char *) %d\n", aWidth);
00487 
00488     return NS_OK;
00489 }
00490 
00491 nsresult
00492 nsFontMetricsPango::GetWidth(const PRUnichar* aString, PRUint32 aLength,
00493                              nscoord& aWidth, PRInt32 *aFontID,
00494                              nsRenderingContextGTK *aContext)
00495 {
00496     nsresult rv = NS_OK;
00497     PangoLayout *layout = pango_layout_new(mPangoContext);
00498 
00499     gchar *text = g_utf16_to_utf8(aString, aLength,
00500                                   NULL, NULL, NULL);
00501 
00502     if (!text) {
00503         aWidth = 0;
00504 #ifdef DEBUG
00505         NS_WARNING("nsFontMetricsPango::GetWidth invalid unicode to follow");
00506         DUMP_PRUNICHAR(aString, aLength)
00507 #endif
00508         rv = NS_ERROR_FAILURE;
00509         goto loser;
00510     }
00511 
00512     gint width, height;
00513 
00514     pango_layout_set_text(layout, text, strlen(text));
00515     FixupSpaceWidths(layout, text);
00516     pango_layout_get_size(layout, &width, &height);
00517 
00518     float f;
00519     f = mDeviceContext->DevUnitsToAppUnits();
00520     aWidth = NSToCoordRound(width * f / PANGO_SCALE);
00521 
00522     //    printf("GetWidth %d\n", aWidth);
00523 
00524  loser:
00525     g_free(text);
00526     g_object_unref(layout);
00527 
00528     return rv;
00529 }
00530 
00531 
00532 nsresult
00533 nsFontMetricsPango::GetTextDimensions(const PRUnichar* aString,
00534                                       PRUint32 aLength,
00535                                       nsTextDimensions& aDimensions, 
00536                                       PRInt32* aFontID,
00537                                       nsRenderingContextGTK *aContext)
00538 {
00539     nsresult rv = NS_OK;
00540 
00541     PangoLayout *layout = pango_layout_new(mPangoContext);
00542 
00543     gchar *text = g_utf16_to_utf8(aString, aLength,
00544                                   NULL, NULL, NULL);
00545 
00546     if (!text) {
00547 #ifdef DEBUG
00548         NS_WARNING("nsFontMetricsPango::GetTextDimensions invalid unicode to follow");
00549         DUMP_PRUNICHAR(aString, aLength)
00550 #endif
00551         aDimensions.width = 0;
00552         aDimensions.ascent = 0;
00553         aDimensions.descent = 0;
00554 
00555         rv = NS_ERROR_FAILURE;
00556         goto loser;
00557     }
00558         
00559 
00560     pango_layout_set_text(layout, text, strlen(text));
00561     FixupSpaceWidths(layout, text);
00562 
00563     // Get the logical extents
00564     PangoLayoutLine *line;
00565     if (pango_layout_get_line_count(layout) != 1) {
00566         printf("Warning: more than one line!\n");
00567     }
00568     line = pango_layout_get_line(layout, 0);
00569 
00570     PangoRectangle rect;
00571     pango_layout_line_get_extents(line, NULL, &rect);
00572 
00573     float P2T;
00574     P2T = mDeviceContext->DevUnitsToAppUnits();
00575 
00576     aDimensions.width = NSToCoordRound(rect.width * P2T / PANGO_SCALE);
00577     aDimensions.ascent = NSToCoordRound(PANGO_ASCENT(rect) * P2T / PANGO_SCALE);
00578     aDimensions.descent = NSToCoordRound(PANGO_DESCENT(rect) * P2T / PANGO_SCALE);
00579 
00580     //    printf("GetTextDimensions %d %d %d\n", aDimensions.width,
00581     //aDimensions.ascent, aDimensions.descent);
00582 
00583  loser:
00584     g_free(text);
00585     g_object_unref(layout);
00586 
00587     return rv;
00588 }
00589 
00590 nsresult
00591 nsFontMetricsPango::GetTextDimensions(const char*         aString,
00592                                       PRInt32             aLength,
00593                                       PRInt32             aAvailWidth,
00594                                       PRInt32*            aBreaks,
00595                                       PRInt32             aNumBreaks,
00596                                       nsTextDimensions&   aDimensions,
00597                                       PRInt32&            aNumCharsFit,
00598                                       nsTextDimensions&   aLastWordDimensions,
00599                                       PRInt32*            aFontID,
00600                                       nsRenderingContextGTK *aContext)
00601 {
00602 
00603     return GetTextDimensionsInternal(aString, aLength, aAvailWidth, aBreaks,
00604                                      aNumBreaks, aDimensions, aNumCharsFit,
00605                                      aLastWordDimensions, aContext);
00606 
00607 }
00608 
00609 nsresult
00610 nsFontMetricsPango::GetTextDimensions(const PRUnichar*    aString,
00611                                       PRInt32             aLength,
00612                                       PRInt32             aAvailWidth,
00613                                       PRInt32*            aBreaks,
00614                                       PRInt32             aNumBreaks,
00615                                       nsTextDimensions&   aDimensions,
00616                                       PRInt32&            aNumCharsFit,
00617                                       nsTextDimensions&   aLastWordDimensions,
00618                                       PRInt32*            aFontID,
00619                                       nsRenderingContextGTK *aContext)
00620 {
00621     nsresult rv = NS_OK;
00622     PRInt32 curBreak = 0;
00623     gchar *curChar;
00624 
00625     PRInt32 *utf8Breaks = new PRInt32[aNumBreaks];
00626 
00627     gchar *text = g_utf16_to_utf8(aString, (PRInt32)aLength,
00628                                   NULL, NULL, NULL);
00629 
00630     curChar = text;
00631 
00632     if (!text) {
00633 #ifdef DEBUG
00634         NS_WARNING("nsFontMetricsPango::GetWidth invalid unicode to follow");
00635         DUMP_PRUNICHAR(aString, (PRUint32)aLength)
00636 #endif
00637         rv = NS_ERROR_FAILURE;
00638         goto loser;
00639     }
00640 
00641     // Covert the utf16 break offsets to utf8 break offsets
00642     for (PRInt32 curOffset=0; curOffset < aLength;
00643          curOffset++, curChar = g_utf8_find_next_char(curChar, NULL)) {
00644         if (aBreaks[curBreak] == curOffset) {
00645             utf8Breaks[curBreak] = curChar - text;
00646             curBreak++;
00647         }
00648 
00649         if (IS_HIGH_SURROGATE(aString[curOffset]))
00650             curOffset++;
00651     }
00652 
00653     // Always catch the last break
00654     utf8Breaks[curBreak] = curChar - text;
00655 
00656 #if 0
00657     if (strlen(text) != aLength) {
00658         printf("Different lengths for utf16 %d and utf8 %d\n", aLength, strlen(text));
00659         DUMP_PRUNICHAR(aString, aLength)
00660         DUMP_PRUNICHAR(text, strlen(text))
00661         for (PRInt32 i = 0; i < aNumBreaks; ++i) {
00662             printf("  break %d utf16 %d utf8 %d\n", i, aBreaks[i], utf8Breaks[i]);
00663         }
00664     }
00665 #endif
00666 
00667     // We'll use curBreak to indicate which of the breaks end up being
00668     // used for the break point for this line.
00669     curBreak = 0;
00670     rv = GetTextDimensionsInternal(text, strlen(text), aAvailWidth, utf8Breaks,
00671                                    aNumBreaks, aDimensions, aNumCharsFit,
00672                                    aLastWordDimensions, aContext);
00673 
00674     // Figure out which of the breaks we ended up using to convert
00675     // back to utf16 - start from the end.
00676     for (PRInt32 i = aNumBreaks - 1; i >= 0; --i) {
00677         if (utf8Breaks[i] == aNumCharsFit) {
00678             //      if (aNumCharsFit != aBreaks[i])
00679             //                printf("Fixing utf8 -> utf16 %d -> %d\n", aNumCharsFit, aBreaks[i]);
00680             aNumCharsFit = aBreaks[i];
00681             break;
00682         }
00683     }
00684 
00685  loser:
00686     if (text)
00687         g_free(text);
00688 
00689     delete[] utf8Breaks;
00690 
00691     return rv;
00692 }
00693 
00694 nsresult
00695 nsFontMetricsPango::DrawString(const char *aString, PRUint32 aLength,
00696                                nscoord aX, nscoord aY,
00697                                const nscoord* aSpacing,
00698                                nsRenderingContextGTK *aContext,
00699                                nsDrawingSurfaceGTK *aSurface)
00700 {
00701     PangoLayout *layout = pango_layout_new(mPangoContext);
00702 
00703     pango_layout_set_text(layout, aString, aLength);
00704     FixupSpaceWidths(layout, aString);
00705 
00706     int x = aX;
00707     int y = aY;
00708 
00709     aContext->GetTranMatrix()->TransformCoord(&x, &y);
00710 
00711     PangoLayoutLine *line;
00712     if (pango_layout_get_line_count(layout) != 1) {
00713         printf("Warning: more than one line!\n");
00714     }
00715     line = pango_layout_get_line(layout, 0);
00716 
00717     aContext->UpdateGC();
00718     GdkGC *gc = aContext->GetGC();
00719 
00720     if (aSpacing && *aSpacing) {
00721         DrawStringSlowly(aString, NULL, aLength, aSurface->GetDrawable(),
00722                          gc, x, y, line, aSpacing);
00723     }
00724     else {
00725         gdk_draw_layout_line(aSurface->GetDrawable(), gc,
00726                              x, y,
00727                              line);
00728     }
00729 
00730     g_object_unref(gc);
00731     g_object_unref(layout);
00732 
00733     //    printf("DrawString (char *)\n");
00734 
00735     return NS_OK;
00736 }
00737 
00738 nsresult
00739 nsFontMetricsPango::DrawString(const PRUnichar* aString, PRUint32 aLength,
00740                                nscoord aX, nscoord aY,
00741                                PRInt32 aFontID,
00742                                const nscoord* aSpacing,
00743                                nsRenderingContextGTK *aContext,
00744                                nsDrawingSurfaceGTK *aSurface)
00745 {
00746     nsresult rv = NS_OK;
00747     int x = aX;
00748     int y = aY;
00749 
00750     aContext->UpdateGC();
00751     GdkGC *gc = aContext->GetGC();
00752 
00753     PangoLayout *layout = pango_layout_new(mPangoContext);
00754 
00755     gchar *text = g_utf16_to_utf8(aString, aLength,
00756                                   NULL, NULL, NULL);
00757 
00758     if (!text) {
00759 #ifdef DEBUG
00760         NS_WARNING("nsFontMetricsPango::DrawString invalid unicode to follow");
00761         DUMP_PRUNICHAR(aString, aLength)
00762 #endif
00763         rv = NS_ERROR_FAILURE;
00764         goto loser;
00765     }
00766 
00767     pango_layout_set_text(layout, text, strlen(text));
00768     FixupSpaceWidths(layout, text);
00769 
00770     aContext->GetTranMatrix()->TransformCoord(&x, &y);
00771 
00772     PangoLayoutLine *line;
00773     if (pango_layout_get_line_count(layout) != 1) {
00774         printf("Warning: more than one line!\n");
00775     }
00776     line = pango_layout_get_line(layout, 0);
00777 
00778     if (aSpacing && *aSpacing) {
00779         DrawStringSlowly(text, aString, aLength, aSurface->GetDrawable(),
00780                          gc, x, y, line, aSpacing);
00781     }
00782     else {
00783         gdk_draw_layout_line(aSurface->GetDrawable(), gc,
00784                              x, y,
00785                              line);
00786     }
00787 
00788  loser:
00789 
00790     g_free(text);
00791     g_object_unref(gc);
00792     g_object_unref(layout);
00793 
00794     //    printf("DrawString\n");
00795 
00796     return rv;
00797 }
00798 
00799 #ifdef MOZ_MATHML
00800 nsresult
00801 nsFontMetricsPango::GetBoundingMetrics(const char *aString, PRUint32 aLength,
00802                                        nsBoundingMetrics &aBoundingMetrics,
00803                                        nsRenderingContextGTK *aContext)
00804 {
00805     printf("GetBoundingMetrics (char *)\n");
00806     return NS_ERROR_FAILURE;
00807 }
00808 
00809 nsresult
00810 nsFontMetricsPango::GetBoundingMetrics(const PRUnichar *aString,
00811                                        PRUint32 aLength,
00812                                        nsBoundingMetrics &aBoundingMetrics,
00813                                        PRInt32 *aFontID,
00814                                        nsRenderingContextGTK *aContext)
00815 {
00816     nsresult rv = NS_OK;
00817     PangoLayout *layout = pango_layout_new(mPangoContext);
00818 
00819     gchar *text = g_utf16_to_utf8(aString, aLength,
00820                                   NULL, NULL, NULL);
00821 
00822     if (!text) {
00823 #ifdef DEBUG
00824         NS_WARNING("nsFontMetricsPango::GetBoundingMetrics invalid unicode to follow");
00825         DUMP_PRUNICHAR(aString, aLength)
00826 #endif
00827         aBoundingMetrics.Clear();
00828 
00829         rv = NS_ERROR_FAILURE;
00830         goto loser;
00831     }
00832 
00833     pango_layout_set_text(layout, text, -1);
00834     FixupSpaceWidths(layout, text);
00835 
00836     PangoLayoutLine *line;
00837     if (pango_layout_get_line_count(layout) != 1) {
00838         printf("Warning: more than one line!\n");
00839     }
00840     line = pango_layout_get_line(layout, 0);
00841 
00842     // Get the ink and logical extents
00843     PangoRectangle ink, logical;
00844     pango_layout_line_get_extents(line, &ink, &logical);
00845 
00846     float P2T;
00847     P2T = mDeviceContext->DevUnitsToAppUnits();
00848 
00849     aBoundingMetrics.leftBearing  = NSToCoordRound(PANGO_LBEARING(ink) * P2T / PANGO_SCALE);
00850     aBoundingMetrics.rightBearing = NSToCoordRound(PANGO_RBEARING(ink) * P2T / PANGO_SCALE);
00851     aBoundingMetrics.ascent       = NSToCoordRound(PANGO_ASCENT(ink)   * P2T / PANGO_SCALE);
00852     aBoundingMetrics.descent      = NSToCoordRound(PANGO_DESCENT(ink)  * P2T / PANGO_SCALE);
00853     aBoundingMetrics.width        = NSToCoordRound(logical.width       * P2T / PANGO_SCALE);
00854 
00855  loser:
00856     g_free(text);
00857     g_object_unref(layout);
00858 
00859     return rv;
00860 }
00861 
00862 #endif /* MOZ_MATHML */
00863 
00864 GdkFont*
00865 nsFontMetricsPango::GetCurrentGDKFont(void)
00866 {
00867     return nsnull;
00868 }
00869 
00870 nsresult
00871 nsFontMetricsPango::SetRightToLeftText(PRBool aIsRTL)
00872 {
00873     if (aIsRTL) {
00874         if (!mRTLPangoContext) {
00875             mRTLPangoContext = gdk_pango_context_get();
00876             pango_context_set_base_dir(mRTLPangoContext, PANGO_DIRECTION_RTL);
00877 
00878             gdk_pango_context_set_colormap(mRTLPangoContext, gdk_rgb_get_cmap());
00879             pango_context_set_language(mRTLPangoContext, GetPangoLanguage(mLangGroup));
00880             pango_context_set_font_description(mRTLPangoContext, mPangoFontDesc);
00881         }
00882         mPangoContext = mRTLPangoContext;
00883     }
00884     else {
00885         mPangoContext = mLTRPangoContext;
00886     }
00887 
00888     mIsRTL = aIsRTL;
00889     return NS_OK;
00890 }
00891 
00892 PRBool
00893 nsFontMetricsPango::GetRightToLeftText()
00894 {
00895     return mIsRTL;
00896 }
00897 
00898 nsresult
00899 nsFontMetricsPango::GetClusterInfo(const PRUnichar *aText,
00900                                    PRUint32 aLength,
00901                                    PRUint8 *aClusterStarts)
00902 {
00903     nsresult rv = NS_OK;
00904     PangoLogAttr *attrs = NULL;
00905     gint n_attrs = 0;
00906     PangoLayout *layout = pango_layout_new(mPangoContext);
00907     
00908     // Convert the incoming UTF-16 to UTF-8
00909     gchar *text = g_utf16_to_utf8(aText, aLength, NULL, NULL, NULL);
00910 
00911     if (!text) {
00912 #ifdef DEBUG
00913         NS_WARNING("nsFontMetricsPango::GetWidth invalid unicode to follow");
00914         DUMP_PRUNICHAR(aText, aLength)
00915 #endif
00916         rv = NS_ERROR_FAILURE;
00917         goto loser;
00918     }
00919 
00920     // Set up the pango layout
00921     pango_layout_set_text(layout, text, strlen(text));
00922     FixupSpaceWidths(layout, text);
00923 
00924     // Convert back to UTF-16 while filling in the cluster info
00925     // structure.
00926     pango_layout_get_log_attrs(layout, &attrs, &n_attrs);
00927 
00928     for (PRUint32 pos = 0; pos < aLength; pos++) {
00929         if (IS_HIGH_SURROGATE(aText[pos])) {
00930             aClusterStarts[pos] = 1;
00931             pos++;
00932         }
00933         else {
00934             aClusterStarts[pos] = attrs[pos].is_cursor_position;
00935         }
00936     }
00937 
00938  loser:
00939     if (attrs)
00940         g_free(attrs);
00941     if (text)
00942         g_free(text);
00943     if (layout)
00944         g_object_unref(layout);
00945 
00946     return rv;
00947 }
00948 
00949 PRInt32
00950 nsFontMetricsPango::GetPosition(const PRUnichar *aText, PRUint32 aLength,
00951                                 nsPoint aPt)
00952 {
00953     int trailing = 0;
00954     int inx = 0;
00955     gboolean found = FALSE;
00956     const gchar *curChar;
00957     PRInt32 retval = 0;
00958 
00959     float f = mDeviceContext->AppUnitsToDevUnits();
00960     
00961     PangoLayout *layout = pango_layout_new(mPangoContext);
00962     PRUint32 localX = (PRUint32)(aPt.x * PANGO_SCALE * f);
00963     PRUint32 localY = (PRUint32)(aPt.y * PANGO_SCALE * f);
00964 
00965     // Convert the incoming UTF-16 to UTF-8
00966     gchar *text = g_utf16_to_utf8(aText, aLength, NULL, NULL, NULL);
00967 
00968     if (!text) {
00969 #ifdef DEBUG
00970         NS_WARNING("nsFontMetricsPango::GetWidth invalid unicode to follow");
00971         DUMP_PRUNICHAR(aText, aLength)
00972 #endif
00973         retval = -1;
00974         goto loser;
00975     }
00976 
00977     // Set up the pango layout
00978     pango_layout_set_text(layout, text, strlen(text));
00979     FixupSpaceWidths(layout, text);
00980     
00981     found = pango_layout_xy_to_index(layout, localX, localY,
00982                                      &inx, &trailing);
00983 
00984     // Convert the index back to the utf-16 index
00985     curChar = text;
00986 
00987     // Jump to the end if it's not found.
00988     if (!found) {
00989         if (inx == 0)
00990             retval = 0;
00991         else if (trailing)
00992             retval = aLength;
00993 
00994         goto loser;
00995     }
00996 
00997     for (PRUint32 curOffset=0; curOffset < aLength;
00998          curOffset++, curChar = g_utf8_find_next_char(curChar, NULL)) {
00999 
01000         // Check for a match before checking for a surrogate pair
01001         if (curChar - text == inx) {
01002             retval = curOffset;
01003             break;
01004         }
01005 
01006         if (IS_HIGH_SURROGATE(aText[curOffset]))
01007             curOffset++;
01008     }
01009 
01010     // If there was a trailing result, advance the index pointer the
01011     // number of characters equal to the trailing result.
01012     while (trailing) {
01013         retval++;
01014         // Yes, this can make aInx > length to indicate the end of the
01015         // string.
01016         if (retval < (PRInt32)aLength && IS_HIGH_SURROGATE(aText[retval]))
01017             retval++;
01018         trailing--;
01019     }
01020 
01021  loser:
01022     if (text)
01023         g_free(text);
01024     if (layout)
01025         g_object_unref(layout);
01026 
01027     return retval;
01028 }
01029 
01030 nsresult
01031 nsFontMetricsPango::GetRangeWidth(const PRUnichar *aText,
01032                                   PRUint32 aLength,
01033                                   PRUint32 aStart,
01034                                   PRUint32 aEnd,
01035                                   PRUint32 &aWidth)
01036 {
01037     nsresult rv = NS_OK;
01038     PRUint32 utf8Start = 0;
01039     PRUint32 utf8End = 0;
01040 
01041     aWidth = 0;
01042 
01043     // Convert the incoming UTF-16 to UTF-8
01044     gchar *text = g_utf16_to_utf8(aText, aLength, NULL, NULL, NULL);
01045     gchar *curChar = text;
01046 
01047     if (!text) {
01048 #ifdef DEBUG
01049         NS_WARNING("nsFontMetricsPango::GetWidth invalid unicode to follow");
01050         DUMP_PRUNICHAR(aText, aLength)
01051 #endif
01052         rv = NS_ERROR_FAILURE;
01053         goto loser;
01054     }
01055 
01056     // Convert the utf16 offsets into utf8 offsets
01057     for (PRUint32 curOffset = 0; curOffset < aLength;
01058          curOffset++, curChar = g_utf8_find_next_char(curChar, NULL)) {
01059 
01060         if (curOffset == aStart)
01061             utf8Start = curChar - text;
01062 
01063         if (curOffset == aEnd)
01064             utf8End = curChar - text;
01065         
01066         if (IS_HIGH_SURROGATE(aText[curOffset]))
01067             curOffset++;
01068     }
01069 
01070     // Special case where the end index is the same as the length
01071     if (aLength == aEnd)
01072         utf8End = strlen(text);
01073 
01074     rv = GetRangeWidth(text, strlen(text), utf8Start, utf8End, aWidth);
01075 
01076  loser:
01077     if (text)
01078         g_free(text);
01079 
01080     return rv;
01081 }
01082 
01083 nsresult
01084 nsFontMetricsPango::GetRangeWidth(const char *aText,
01085                                   PRUint32 aLength,
01086                                   PRUint32 aStart,
01087                                   PRUint32 aEnd,
01088                                   PRUint32 &aWidth)
01089 {
01090     nsresult rv = NS_OK;
01091     int *ranges = NULL;
01092     int n_ranges = 0;
01093     float f;
01094 
01095     aWidth = 0;
01096 
01097     PangoLayout *layout = pango_layout_new(mPangoContext);
01098 
01099     if (!aText) {
01100         rv = NS_ERROR_FAILURE;
01101         goto loser;
01102     }
01103 
01104     pango_layout_set_text(layout, aText, aLength);
01105     FixupSpaceWidths(layout, aText);
01106 
01107     PangoLayoutLine *line;
01108     if (pango_layout_get_line_count(layout) != 1) {
01109         printf("Warning: more than one line!\n");
01110     }
01111     line = pango_layout_get_line(layout, 0);
01112 
01113     pango_layout_line_get_x_ranges(line, aStart, aEnd, &ranges, &n_ranges);
01114 
01115     aWidth = (ranges[((n_ranges - 1) * 2) + 1] - ranges[0]);
01116 
01117     f = mDeviceContext-> DevUnitsToAppUnits();
01118     aWidth = nscoord(aWidth * f / PANGO_SCALE);
01119 
01120  loser:
01121     if (ranges)
01122         g_free(ranges);
01123     if (layout)
01124         g_object_unref(layout);
01125 
01126     return rv;
01127 }
01128 
01129 /* static */
01130 PRUint32
01131 nsFontMetricsPango::GetHints(void)
01132 {
01133     return (NS_RENDERING_HINT_BIDI_REORDERING |
01134             NS_RENDERING_HINT_ARABIC_SHAPING | 
01135             NS_RENDERING_HINT_FAST_MEASURE |
01136             NS_RENDERING_HINT_REORDER_SPACED_TEXT |
01137             NS_RENDERING_HINT_TEXT_CLUSTERS);
01138 }
01139 
01140 /* static */
01141 nsresult
01142 nsFontMetricsPango::FamilyExists(nsIDeviceContext *aDevice,
01143                                  const nsString &aName)
01144 {
01145     // fontconfig family name is always in UTF-8
01146     NS_ConvertUTF16toUTF8 name(aName);
01147 
01148     nsresult rv = NS_ERROR_FAILURE;
01149     PangoContext *context = gdk_pango_context_get();
01150     PangoFontFamily **familyList;
01151     int n;
01152 
01153     pango_context_list_families(context, &familyList, &n);
01154 
01155     for (int i=0; i < n; i++) {
01156         const char *tmpname = pango_font_family_get_name(familyList[i]);
01157         if (!Compare(nsDependentCString(tmpname), name,
01158                      nsCaseInsensitiveCStringComparator())) {
01159             rv = NS_OK;
01160             break;
01161         }
01162     }
01163 
01164     g_free(familyList);
01165     g_object_unref(context);
01166 
01167     return rv;
01168 }
01169 
01170 // Private Methods
01171 
01172 nsresult
01173 nsFontMetricsPango::RealizeFont(void)
01174 {
01175     nsCString familyList;
01176     // Create and fill out the font description.
01177     mPangoFontDesc = pango_font_description_new();
01178 
01179     // Add CSS names - walk the list of fonts, adding the generic as
01180     // the last font
01181     for (int i=0; i < mFontList.Count(); ++i) {
01182         // if this was a generic name, break out of the loop since we
01183         // don't want to add it to the pattern yet
01184         if (mFontIsGeneric[i])
01185             break;;
01186 
01187         nsCString *familyName = mFontList.CStringAt(i);
01188         familyList.Append(familyName->get());
01189         familyList.Append(',');
01190     }
01191 
01192     // If there's a generic add a pref for the generic if there's one
01193     // set.
01194     if (mGenericFont && !mFont.systemFont) {
01195         nsCString name;
01196         name += "font.name.";
01197         name += mGenericFont->get();
01198         name += ".";
01199 
01200         nsString langGroup;
01201         mLangGroup->ToString(langGroup);
01202 
01203         name.AppendWithConversion(langGroup);
01204 
01205         nsCOMPtr<nsIPref> pref;
01206         pref = do_GetService(NS_PREF_CONTRACTID);
01207         if (pref) {
01208             nsresult rv;
01209             nsXPIDLCString value;
01210             rv = pref->GetCharPref(name.get(), getter_Copies(value));
01211 
01212             // we ignore prefs that have three hypens since they are X
01213             // style prefs.
01214             if (NS_FFRECountHyphens(value) < 3) {
01215                 nsCString tmpstr;
01216                 tmpstr.Append(value);
01217 
01218                 familyList.Append(tmpstr);
01219                 familyList.Append(',');
01220             }
01221         }
01222     }
01223 
01224     // Add the generic if there is one.
01225     if (mGenericFont && !mFont.systemFont) {
01226         familyList.Append(mGenericFont->get());
01227         familyList.Append(',');
01228     }
01229 
01230     // Set the family
01231     pango_font_description_set_family(mPangoFontDesc,
01232                                       familyList.get());
01233 
01234     // Set the point size
01235     pango_font_description_set_size(mPangoFontDesc,
01236                                     (gint)(mPointSize * PANGO_SCALE));
01237 
01238     // Set the style
01239     pango_font_description_set_style(mPangoFontDesc,
01240                                      CalculateStyle(mFont.style));
01241 
01242     // Set the weight
01243     pango_font_description_set_weight(mPangoFontDesc,
01244                                       CalculateWeight(mFont.weight));
01245 
01246     // Now that we have the font description set up, create the
01247     // context.
01248     mLTRPangoContext = gdk_pango_context_get();
01249     mPangoContext = mLTRPangoContext;
01250 
01251     // Make sure to set the base direction to LTR - if layout needs to
01252     // render RTL text it will use ::SetRightToLeftText()
01253     pango_context_set_base_dir(mPangoContext, PANGO_DIRECTION_LTR);
01254 
01255     // Set the color map so we can draw later.
01256     gdk_pango_context_set_colormap(mPangoContext, gdk_rgb_get_cmap());
01257 
01258     // Set the pango language now that we have a context
01259     pango_context_set_language(mPangoContext, GetPangoLanguage(mLangGroup));
01260 
01261     // And attach the font description to this context
01262     pango_context_set_font_description(mPangoContext, mPangoFontDesc);
01263 
01264     return NS_OK;
01265 }
01266 
01267 /* static */
01268 PRBool
01269 nsFontMetricsPango::EnumFontCallback(const nsString &aFamily,
01270                                      PRBool aIsGeneric, void *aData)
01271 {
01272     NS_ConvertUTF16toUTF8 name(aFamily);
01273 
01274     // The newest fontconfig does the full Unicode case folding so that 
01275     // we're being lazy here by calling |ToLowerCase| after converting
01276     // to UTF-8  assuming that in virtually all cases, we just have to
01277     // fold [A-Z].  (bug 223653). 
01278     ToLowerCase(name);
01279     nsFontMetricsPango *metrics = (nsFontMetricsPango *)aData;
01280     metrics->mFontList.AppendCString(name);
01281     metrics->mFontIsGeneric.AppendElement((void *)aIsGeneric);
01282     if (aIsGeneric) {
01283         metrics->mGenericFont = 
01284             metrics->mFontList.CStringAt(metrics->mFontList.Count() - 1);
01285         return PR_FALSE; // stop processing
01286     }
01287 
01288     return PR_TRUE; // keep processing
01289 }
01290 
01291 /*
01292  * This is only used when there's per-character spacing happening.
01293  * Well, really it can be either line or character spacing but it's
01294  * just turtles all the way down!
01295  */
01296 
01297 void
01298 nsFontMetricsPango::DrawStringSlowly(const gchar *aText,
01299                                      const PRUnichar *aOrigString,
01300                                      PRUint32 aLength,
01301                                      GdkDrawable *aDrawable,
01302                                      GdkGC *aGC, gint aX, gint aY,
01303                                      PangoLayoutLine *aLine,
01304                                      const nscoord *aSpacing)
01305 {
01306     float app2dev;
01307     app2dev = mDeviceContext->AppUnitsToDevUnits();
01308     gint offset = 0;
01309 
01310     /*
01311      * We walk the list of glyphs returned in each layout run,
01312      * matching up the glyphs with the characters in the source text.
01313      * We use the aSpacing argument to figure out where to place those
01314      * glyphs.  It's important to note that since the string we're
01315      * working with is in UTF-8 while the spacing argument assumes
01316      * that offset will be part of the UTF-16 string.  Logical
01317      * attributes in pango are in byte offsets in the UTF-8 string, so
01318      * we need to store the offsets based on the UTF-8 string.
01319      */
01320     nscoord *utf8spacing = new nscoord[strlen(aText)];
01321 
01322     if (aOrigString) {
01323         const gchar *curChar = aText;
01324         bzero(utf8spacing, sizeof(nscoord) * strlen(aText));
01325 
01326         // Covert the utf16 spacing offsets to utf8 spacing offsets
01327         for (PRUint32 curOffset=0; curOffset < aLength;
01328              curOffset++, curChar = g_utf8_find_next_char(curChar, NULL)) {
01329             utf8spacing[curChar - aText] = aSpacing[curOffset];
01330 
01331             if (IS_HIGH_SURROGATE(aOrigString[curOffset]))
01332                 curOffset++;
01333         }
01334     }
01335     else {
01336         memcpy(utf8spacing, aSpacing, (sizeof(nscoord *) * aLength));
01337     }
01338 
01339     gint curRun = 0;
01340 
01341     for (GSList *tmpList = aLine->runs; tmpList && tmpList->data;
01342          tmpList = tmpList->next, curRun++) {
01343         PangoLayoutRun *layoutRun = (PangoLayoutRun *)tmpList->data;
01344         gint tmpOffset = 0;
01345 
01346         /*        printf("    Rendering run %d: \"%s\"\n", curRun,
01347                   &aText[layoutRun->item->offset]); */
01348 
01349         for (gint i=0; i < layoutRun->glyphs->num_glyphs; i++) {
01350             /* printf("glyph %d offset %d orig width %d new width %d\n", i,
01351              *        layoutRun->glyphs->log_clusters[i] + layoutRun->item->offset,
01352              *        layoutRun->glyphs->glyphs[i].geometry.width,
01353              *       (gint)(utf8spacing[layoutRun->glyphs->log_clusters[i] + layoutRun->item->offset] * app2dev * PANGO_SCALE));
01354              */
01355             gint thisOffset = (gint)(utf8spacing[layoutRun->glyphs->log_clusters[i] + layoutRun->item->offset]
01356                                      * app2dev * PANGO_SCALE);
01357             layoutRun->glyphs->glyphs[i].geometry.width = thisOffset;
01358             tmpOffset += thisOffset;
01359         }
01360 
01361         /*        printf("    rendering at X coord %d\n", aX + offset); */
01362         offset += tmpOffset;
01363     }
01364 
01365     gdk_draw_layout_line(aDrawable, aGC, aX, aY, aLine);
01366 
01367     delete[] utf8spacing;
01368 }
01369 
01370 nsresult
01371 nsFontMetricsPango::GetTextDimensionsInternal(const gchar*        aString,
01372                                               PRInt32             aLength,
01373                                               PRInt32             aAvailWidth,
01374                                               PRInt32*            aBreaks,
01375                                               PRInt32             aNumBreaks,
01376                                               nsTextDimensions&   aDimensions,
01377                                               PRInt32&            aNumCharsFit,
01378                                               nsTextDimensions&   aLastWordDimensions,
01379                                               nsRenderingContextGTK *aContext)
01380 {
01381     NS_PRECONDITION(aBreaks[aNumBreaks - 1] == aLength, "invalid break array");
01382 
01383     // If we need to back up this state represents the last place
01384     // we could break. We can use this to avoid remeasuring text
01385     PRInt32 prevBreakState_BreakIndex = -1; // not known
01386                                             // (hasn't been computed)
01387     nscoord prevBreakState_Width = 0; // accumulated width to this point
01388 
01389     // Initialize OUT parameters
01390     GetMaxAscent(aLastWordDimensions.ascent);
01391     GetMaxDescent(aLastWordDimensions.descent);
01392     aLastWordDimensions.width = -1;
01393     aNumCharsFit = 0;
01394 
01395     // Iterate each character in the string and determine which font to use
01396     nscoord width = 0;
01397     PRInt32 start = 0;
01398     nscoord aveCharWidth;
01399     GetAveCharWidth(aveCharWidth);
01400 
01401     while (start < aLength) {
01402         // Estimate how many characters will fit. Do that by
01403         // diving the available space by the average character
01404         // width. Make sure the estimated number of characters is
01405         // at least 1
01406         PRInt32 estimatedNumChars = 0;
01407 
01408         if (aveCharWidth > 0)
01409             estimatedNumChars = (aAvailWidth - width) / aveCharWidth;
01410 
01411         if (estimatedNumChars < 1)
01412             estimatedNumChars = 1;
01413 
01414         // Find the nearest break offset
01415         PRInt32 estimatedBreakOffset = start + estimatedNumChars;
01416         PRInt32 breakIndex;
01417         nscoord numChars;
01418 
01419         // Find the nearest place to break that is less than or equal to
01420         // the estimated break offset
01421         if (aLength <= estimatedBreakOffset) {
01422             // All the characters should fit
01423             numChars = aLength - start;
01424             breakIndex = aNumBreaks - 1;
01425         } 
01426         else {
01427             breakIndex = prevBreakState_BreakIndex;
01428             while (((breakIndex + 1) < aNumBreaks) &&
01429                    (aBreaks[breakIndex + 1] <= estimatedBreakOffset)) {
01430                 ++breakIndex;
01431             }
01432 
01433             if (breakIndex == prevBreakState_BreakIndex) {
01434                 ++breakIndex; // make sure we advanced past the
01435                 // previous break index
01436             }
01437 
01438             numChars = aBreaks[breakIndex] - start;
01439         }
01440 
01441         // Measure the text
01442         nscoord twWidth = 0;
01443         if ((1 == numChars) && (aString[start] == ' '))
01444             GetSpaceWidth(twWidth);
01445         else if (numChars > 0)
01446             GetWidth(&aString[start], numChars, twWidth, aContext);
01447 
01448         // See if the text fits
01449         PRBool  textFits = (twWidth + width) <= aAvailWidth;
01450 
01451         // If the text fits then update the width and the number of
01452         // characters that fit
01453         if (textFits) {
01454             aNumCharsFit += numChars;
01455             width += twWidth;
01456             start += numChars;
01457 
01458             // This is a good spot to back up to if we need to so remember
01459             // this state
01460             prevBreakState_BreakIndex = breakIndex;
01461             prevBreakState_Width = width;
01462         }
01463         else {
01464             // See if we can just back up to the previous saved
01465             // state and not have to measure any text
01466             if (prevBreakState_BreakIndex > 0) {
01467                 // If the previous break index is just before the
01468                 // current break index then we can use it
01469                 if (prevBreakState_BreakIndex == (breakIndex - 1)) {
01470                     aNumCharsFit = aBreaks[prevBreakState_BreakIndex];
01471                     width = prevBreakState_Width;
01472                     break;
01473                 }
01474             }
01475 
01476             // We can't just revert to the previous break state
01477             if (0 == breakIndex) {
01478                 // There's no place to back up to, so even though
01479                 // the text doesn't fit return it anyway
01480                 aNumCharsFit += numChars;
01481                 width += twWidth;
01482                 break;
01483             }
01484 
01485             // Repeatedly back up until we get to where the text
01486             // fits or we're all the way back to the first word
01487             width += twWidth;
01488             while ((breakIndex >= 1) && (width > aAvailWidth)) {
01489                 twWidth = 0;
01490                 start = aBreaks[breakIndex - 1];
01491                 numChars = aBreaks[breakIndex] - start;
01492 
01493                 if ((1 == numChars) && (aString[start] == ' '))
01494                     GetSpaceWidth(twWidth);
01495                 else if (numChars > 0)
01496                     GetWidth(&aString[start], numChars, twWidth,
01497                              aContext);
01498                 width -= twWidth;
01499                 aNumCharsFit = start;
01500                 breakIndex--;
01501             }
01502             break;
01503         }
01504     }
01505 
01506     aDimensions.width = width;
01507     GetMaxAscent(aDimensions.ascent);
01508     GetMaxDescent(aDimensions.descent);
01509 
01510     /*    printf("aDimensions %d %d %d aLastWordDimensions %d %d %d aNumCharsFit %d\n",
01511            aDimensions.width, aDimensions.ascent, aDimensions.descent,
01512            aLastWordDimensions.width, aLastWordDimensions.ascent, aLastWordDimensions.descent,
01513            aNumCharsFit); */
01514 
01515     return NS_OK;
01516 }
01517 
01518 void
01519 nsFontMetricsPango::FixupSpaceWidths (PangoLayout *aLayout,
01520                                       const char *aString)
01521 {
01522     PangoLayoutLine *line = pango_layout_get_line(aLayout, 0);
01523 
01524     gint curRun = 0;
01525 
01526     for (GSList *tmpList = line->runs; tmpList && tmpList->data;
01527          tmpList = tmpList->next, curRun++) {
01528         PangoLayoutRun *layoutRun = (PangoLayoutRun *)tmpList->data;
01529 
01530         for (gint i=0; i < layoutRun->glyphs->num_glyphs; i++) {
01531             gint thisOffset = (gint)layoutRun->glyphs->log_clusters[i] + layoutRun->item->offset;
01532             if (aString[thisOffset] == ' ')
01533                 layoutRun->glyphs->glyphs[i].geometry.width = mPangoSpaceWidth;
01534         }
01535     }
01536 }
01537 
01538 /* static */
01539 PangoLanguage *
01540 GetPangoLanguage(nsIAtom *aLangGroup)
01541 {
01542     // Find the FC lang group for this lang group
01543     nsCAutoString cname;
01544     aLangGroup->ToUTF8String(cname);
01545 
01546     // see if the lang group needs to be translated from mozilla's
01547     // internal mapping into fontconfig's
01548     const MozGtkLangGroup *langGroup;
01549     langGroup = NS_FindFCLangGroup(cname);
01550 
01551     // if there's no lang group, just use the lang group as it was
01552     // passed to us
01553     //
01554     // we're casting away the const here for the strings - should be
01555     // safe.
01556     if (!langGroup)
01557         return pango_language_from_string(cname.get());
01558     else if (langGroup->Lang) 
01559         return pango_language_from_string((char *) langGroup->Lang);
01560 
01561     return pango_language_from_string("en");
01562 }
01563 
01564 /* static */
01565 void
01566 FreeGlobals(void)
01567 {
01568 }
01569 
01570 /* static */
01571 PangoStyle
01572 CalculateStyle(PRUint8 aStyle)
01573 {
01574     switch(aStyle) {
01575     case NS_FONT_STYLE_ITALIC:
01576         return PANGO_STYLE_ITALIC;
01577         break;
01578     case NS_FONT_STYLE_OBLIQUE:
01579         return PANGO_STYLE_OBLIQUE;
01580         break;
01581     }
01582 
01583     return PANGO_STYLE_NORMAL;
01584 }
01585 
01586 /* static */
01587 PangoWeight
01588 CalculateWeight (PRUint16 aWeight)
01589 {
01590     /*
01591      * weights come in two parts crammed into one
01592      * integer -- the "base" weight is weight / 100,
01593      * the rest of the value is the "offset" from that
01594      * weight -- the number of steps to move to adjust
01595      * the weight in the list of supported font weights,
01596      * this value can be negative or positive.
01597      */
01598     PRInt32 baseWeight = (aWeight + 50) / 100;
01599     PRInt32 offset = aWeight - baseWeight * 100;
01600 
01601     /* clip weights to range 0 to 9 */
01602     if (baseWeight < 0)
01603         baseWeight = 0;
01604     if (baseWeight > 9)
01605         baseWeight = 9;
01606 
01607     /* Map from weight value to fcWeights index */
01608     static int fcWeightLookup[10] = {
01609         0, 0, 0, 0, 1, 1, 2, 3, 3, 4,
01610     };
01611 
01612     PRInt32 fcWeight = fcWeightLookup[baseWeight];
01613 
01614     /*
01615      * adjust by the offset value, make sure we stay inside the 
01616      * fcWeights table
01617      */
01618     fcWeight += offset;
01619 
01620     if (fcWeight < 0)
01621         fcWeight = 0;
01622     if (fcWeight > 4)
01623         fcWeight = 4;
01624 
01625     /* Map to final PANGO_WEIGHT value */
01626     static int fcWeights[5] = {
01627         349,
01628         499,
01629         649,
01630         749,
01631         999
01632     };
01633 
01634     return (PangoWeight)fcWeights[fcWeight];
01635 }
01636 
01637 /* static */
01638 nsresult
01639 EnumFontsPango(nsIAtom* aLangGroup, const char* aGeneric,
01640                PRUint32* aCount, PRUnichar*** aResult)
01641 {
01642     FcPattern   *pat = NULL;
01643     FcObjectSet *os  = NULL;
01644     FcFontSet   *fs  = NULL;
01645     nsresult     rv  = NS_ERROR_FAILURE;
01646 
01647     PRUnichar **array = NULL;
01648     PRUint32    narray = 0;
01649     PRInt32     serif = 0, sansSerif = 0, monospace = 0, nGenerics;
01650 
01651     *aCount = 0;
01652     *aResult = nsnull;
01653 
01654     pat = FcPatternCreate();
01655     if (!pat)
01656         goto end;
01657 
01658     os = FcObjectSetBuild(FC_FAMILY, FC_FOUNDRY, NULL);
01659     if (!os)
01660         goto end;
01661 
01662     // take the pattern and add the lang group to it
01663     if (aLangGroup)
01664         NS_AddLangGroup(pat, aLangGroup);
01665 
01666     // get the font list
01667     fs = FcFontList(0, pat, os);
01668 
01669     if (!fs)
01670         goto end;
01671 
01672     if (!fs->nfont) {
01673         rv = NS_OK;
01674         goto end;
01675     }
01676 
01677     // Fontconfig supports 3 generic fonts, "serif", "sans-serif", and
01678     // "monospace", slightly different from CSS's 5.
01679     if (!aGeneric)
01680         serif = sansSerif = monospace = 1;
01681     else if (!strcmp(aGeneric, "serif"))
01682         serif = 1;
01683     else if (!strcmp(aGeneric, "sans-serif"))
01684         sansSerif = 1;
01685     else if (!strcmp(aGeneric, "monospace"))
01686         monospace = 1;
01687     else if (!strcmp(aGeneric, "cursive") || !strcmp(aGeneric, "fantasy"))
01688         serif = sansSerif =  1;
01689     else
01690         NS_NOTREACHED("unexpected generic family");
01691     nGenerics = serif + sansSerif + monospace;
01692 
01693     array = NS_STATIC_CAST(PRUnichar **,
01694                nsMemory::Alloc((fs->nfont + nGenerics) * sizeof(PRUnichar *)));
01695     if (!array)
01696         goto end;
01697 
01698     if (serif) {
01699         PRUnichar *name = ToNewUnicode(NS_LITERAL_STRING("serif"));
01700         if (!name)
01701             goto end;
01702         array[narray++] = name;
01703     }
01704 
01705     if (sansSerif) {
01706         PRUnichar *name = ToNewUnicode(NS_LITERAL_STRING("sans-serif"));
01707         if (!name)
01708             goto end;
01709         array[narray++] = name;
01710     }
01711 
01712     if (monospace) {
01713         PRUnichar *name = ToNewUnicode(NS_LITERAL_STRING("monospace"));
01714         if (!name)
01715             goto end;
01716         array[narray++] = name;
01717     }
01718 
01719     for (int i=0; i < fs->nfont; ++i) {
01720         char *family;
01721 
01722         // if there's no family, just move to the next iteration
01723         if (FcPatternGetString (fs->fonts[i], FC_FAMILY, 0,
01724                                 (FcChar8 **) &family) != FcResultMatch) {
01725             continue;
01726         }
01727 
01728         // fontconfig always returns family names in UTF-8
01729         PRUnichar* name =  UTF8ToNewUnicode(nsDependentCString(family));
01730 
01731         if (!name)
01732             goto end;
01733 
01734         array[narray++] = name;
01735     }
01736 
01737     NS_QuickSort(array + nGenerics, narray - nGenerics, sizeof (PRUnichar*),
01738                  CompareFontNames, nsnull);
01739 
01740     *aCount = narray;
01741     if (narray)
01742         *aResult = array;
01743     else
01744         nsMemory::Free(array);
01745 
01746     rv = NS_OK;
01747 
01748  end:
01749     if (NS_FAILED(rv) && array) {
01750         while (narray)
01751             nsMemory::Free (array[--narray]);
01752         nsMemory::Free (array);
01753     }
01754     if (pat)
01755         FcPatternDestroy(pat);
01756     if (os)
01757         FcObjectSetDestroy(os);
01758     if (fs)
01759         FcFontSetDestroy(fs);
01760 
01761     return rv;
01762 }
01763 
01764 /* static */
01765 int
01766 CompareFontNames (const void* aArg1, const void* aArg2, void* aClosure)
01767 {
01768     const PRUnichar* str1 = *((const PRUnichar**) aArg1);
01769     const PRUnichar* str2 = *((const PRUnichar**) aArg2);
01770 
01771     return nsCRT::strcmp(str1, str2);
01772 }
01773 
01774 
01775 // nsFontEnumeratorPango class
01776 
01777 nsFontEnumeratorPango::nsFontEnumeratorPango()
01778 {
01779 }
01780 
01781 NS_IMPL_ISUPPORTS1(nsFontEnumeratorPango, nsIFontEnumerator)
01782 
01783 NS_IMETHODIMP
01784 nsFontEnumeratorPango::EnumerateAllFonts(PRUint32 *aCount,
01785                                          PRUnichar ***aResult)
01786 {
01787     NS_ENSURE_ARG_POINTER(aResult);
01788     *aResult = nsnull;
01789     NS_ENSURE_ARG_POINTER(aCount);
01790     *aCount = 0;
01791 
01792     return EnumFontsPango(nsnull, nsnull, aCount, aResult);
01793 }
01794 
01795 NS_IMETHODIMP
01796 nsFontEnumeratorPango::EnumerateFonts(const char *aLangGroup,
01797                                       const char *aGeneric,
01798                                       PRUint32 *aCount,
01799                                       PRUnichar ***aResult)
01800 {
01801     NS_ENSURE_ARG_POINTER(aResult);
01802     *aResult = nsnull;
01803     NS_ENSURE_ARG_POINTER(aCount);
01804     *aCount = 0;
01805 
01806     // aLangGroup=null or ""  means any (i.e., don't care)
01807     // aGeneric=null or ""  means any (i.e, don't care)
01808     nsCOMPtr<nsIAtom> langGroup;
01809     if (aLangGroup && *aLangGroup)
01810         langGroup = do_GetAtom(aLangGroup);
01811     const char* generic = nsnull;
01812     if (aGeneric && *aGeneric)
01813         generic = aGeneric;
01814 
01815     return EnumFontsPango(langGroup, generic, aCount, aResult);
01816 }
01817 
01818 NS_IMETHODIMP
01819 nsFontEnumeratorPango::HaveFontFor(const char *aLangGroup,
01820                                    PRBool *aResult)
01821 {
01822     NS_ENSURE_ARG_POINTER(aResult);
01823     *aResult = PR_FALSE;
01824     NS_ENSURE_ARG_POINTER(aLangGroup);
01825 
01826     *aResult = PR_TRUE; // always return true for now.
01827     // Finish me - ftang
01828     return NS_OK;
01829 }
01830 
01831 NS_IMETHODIMP
01832 nsFontEnumeratorPango::GetDefaultFont(const char *aLangGroup,
01833                                       const char *aGeneric,
01834                                       PRUnichar **aResult)
01835 {
01836     NS_ENSURE_ARG_POINTER(aResult);
01837     *aResult = nsnull;
01838 
01839     // Have a look at nsFontEnumeratorXft::GetDefaultFont for some
01840     // possible code for this function.
01841 
01842     return NS_OK;
01843 }
01844 
01845 NS_IMETHODIMP
01846 nsFontEnumeratorPango::UpdateFontList(PRBool *_retval)
01847 {
01848     *_retval = PR_FALSE; // always return false for now
01849     return NS_OK;
01850 }