From c89d0c6fcc109d6840a2d1bbdde9efeef792988b Mon Sep 17 00:00:00 2001 From: "roc+@cs.cmu.edu" Date: Mon, 26 Mar 2007 20:24:49 -0700 Subject: [PATCH] Bug 372629. Draw missing-glyph boxes with the Unicode character inscribed in hex. r=pavlov --- gfx/thebes/public/gfxFont.h | 10 + gfx/thebes/src/Makefile.in | 1 + gfx/thebes/src/gfxAtsuiFonts.cpp | 31 ++- gfx/thebes/src/gfxFont.cpp | 94 ++++++--- gfx/thebes/src/gfxFontMissingGlyphs.cpp | 261 ++++++++++++++++++++++++ gfx/thebes/src/gfxFontMissingGlyphs.h | 67 ++++++ gfx/thebes/src/gfxPangoFonts.cpp | 55 +++-- gfx/thebes/src/gfxWindowsFonts.cpp | 6 +- 8 files changed, 471 insertions(+), 54 deletions(-) create mode 100644 gfx/thebes/src/gfxFontMissingGlyphs.cpp create mode 100644 gfx/thebes/src/gfxFontMissingGlyphs.h diff --git a/gfx/thebes/public/gfxFont.h b/gfx/thebes/public/gfxFont.h index 29826cea854..f02cdaa6ee1 100644 --- a/gfx/thebes/public/gfxFont.h +++ b/gfx/thebes/public/gfxFont.h @@ -774,6 +774,9 @@ public: PRBool IsComplex(PRUint32 aTag) const { return (mValue & (FLAG_IS_SIMPLE_GLYPH|TAG_MASK)) == aTag; } PRBool IsMissing() const { return IsComplex(TAG_MISSING); } PRBool IsComplexCluster() const { return IsComplex(TAG_COMPLEX_CLUSTER); } + PRBool IsComplexOrMissing() const { + return IsComplex(TAG_COMPLEX_CLUSTER) || IsComplex(TAG_MISSING); + } PRBool IsLigatureContinuation() const { return IsComplex(TAG_LIGATURE_CONTINUATION); } PRBool IsClusterContinuation() const { return IsComplex(TAG_CLUSTER_CONTINUATION); } PRBool IsLowSurrogate() const { return IsComplex(TAG_LOW_SURROGATE); } @@ -821,6 +824,8 @@ public: /** This is true for the last DetailedGlyph in the array. This lets * us track the length of the array. */ PRUint32 mIsLastGlyph:1; + /** The glyphID if this is a ComplexCluster, or the Unicode character + * if this is a Missing glyph */ PRUint32 mGlyphID:31; // The advance, x-offset and y-offset of the glyph, in appunits PRInt32 mAdvance; @@ -866,6 +871,8 @@ public: /** * We've found a run of text that should use a particular font. Call this * only during initialization when font substitution has been computed. + * Call it before setting up the glyphs for the characters in this run; + * SetMissingGlyph requires that the correct glyphrun be installed. */ nsresult AddGlyphRun(gfxFont *aFont, PRUint32 aStartCharIndex); void ResetGlyphRuns() { mGlyphRuns.Clear(); } @@ -892,6 +899,7 @@ public: */ void SetDetailedGlyphs(PRUint32 aCharIndex, const DetailedGlyph *aGlyphs, PRUint32 aNumGlyphs); + void SetMissingGlyph(PRUint32 aCharIndex, PRUnichar aChar); // API for access to the raw glyph data, needed by gfxFont::Draw // and gfxFont::GetBoundingBox @@ -911,6 +919,8 @@ public: private: // **** general helpers **** + // Allocate aCount DetailedGlyphs for the given index + DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex, PRUint32 aCount); // Returns the index of the GlyphRun containing the given offset. // Returns mGlyphRuns.Length() when aOffset is mCharacterCount. PRUint32 FindFirstGlyphRunContaining(PRUint32 aOffset); diff --git a/gfx/thebes/src/Makefile.in b/gfx/thebes/src/Makefile.in index 04ce26fedda..5f9170170e1 100644 --- a/gfx/thebes/src/Makefile.in +++ b/gfx/thebes/src/Makefile.in @@ -28,6 +28,7 @@ CPPSRCS = \ gfxFont.cpp \ gfxFontTest.cpp \ gfxMatrix.cpp \ + gfxFontMissingGlyphs.cpp \ gfxPattern.cpp \ gfxPlatform.cpp \ gfxRect.cpp \ diff --git a/gfx/thebes/src/gfxAtsuiFonts.cpp b/gfx/thebes/src/gfxAtsuiFonts.cpp index 2c7d2e2be9c..bd49e2cbdb2 100644 --- a/gfx/thebes/src/gfxAtsuiFonts.cpp +++ b/gfx/thebes/src/gfxAtsuiFonts.cpp @@ -518,7 +518,8 @@ GetAdvanceAppUnits(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount, static void SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount, Fixed *aBaselineDeltas, PRUint32 aAppUnitsPerDevUnit, - gfxTextRun *aRun) + gfxTextRun *aRun, const PRPackedBool *aUnmatched, + const PRUnichar *aString) { NS_ASSERTION(aGlyphCount > 0, "Must set at least one glyph"); PRUint32 firstOffset = aGlyphs[0].originalOffset; @@ -527,12 +528,16 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount, PRUint32 regularGlyphCount = 0; ATSLayoutRecord *displayGlyph = nsnull; PRBool inOrder = PR_TRUE; + PRBool allMatched = PR_TRUE; for (i = 0; i < aGlyphCount; ++i) { ATSLayoutRecord *glyph = &aGlyphs[i]; PRUint32 offset = glyph->originalOffset; firstOffset = PR_MIN(firstOffset, offset); lastOffset = PR_MAX(lastOffset, offset); + if (aUnmatched && aUnmatched[offset/2]) { + allMatched = PR_FALSE; + } if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) { ++regularGlyphCount; displayGlyph = glyph; @@ -542,6 +547,14 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount, } } + if (!allMatched) { + for (i = firstOffset; i <= lastOffset; ++i) { + PRUint32 index = i/2; + aRun->SetMissingGlyph(index, aString[index]); + } + return; + } + gfxTextRun::CompressedGlyph g; PRUint32 offset; for (offset = firstOffset + 2; offset <= lastOffset; offset += 2) { @@ -572,7 +585,7 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount, ATSLayoutRecord *advanceStart = aGlyphs; for (i = 0; i < aGlyphCount; ++i) { ATSLayoutRecord *glyph = &aGlyphs[i]; - if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID || regularGlyphCount == 0) { + if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) { if (detailedGlyphs.Length() > 0) { detailedGlyphs[detailedGlyphs.Length() - 1].mAdvance = GetAdvanceAppUnits(advanceStart, glyph - advanceStart, aAppUnitsPerDevUnit); @@ -598,8 +611,6 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount, detailedGlyphs[detailedGlyphs.Length() - 1].mIsLastGlyph = PR_TRUE; detailedGlyphs[detailedGlyphs.Length() - 1].mAdvance = GetAdvanceAppUnits(advanceStart, aGlyphs + aGlyphCount - advanceStart, aAppUnitsPerDevUnit); - // Should pass unmatchedness here but for now we'll just not tell the textrun - // whether these are "missing glyph" glyphs or not aRun->SetDetailedGlyphs(index, detailedGlyphs.Elements(), detailedGlyphs.Length()); } @@ -653,7 +664,8 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun, aString[stringTailOffset] == ' ') { SetGlyphsForCharacterGroup(glyphRecords + numGlyphs - 1, 1, baselineDeltas ? baselineDeltas + numGlyphs - 1 : nsnull, - appUnitsPerDevUnit, aRun); + appUnitsPerDevUnit, aRun, aUnmatched, + aString); --stringTailOffset; --numGlyphs; } @@ -663,7 +675,8 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun, aString[stringTailOffset] == ' ') { SetGlyphsForCharacterGroup(glyphRecords, 1, baselineDeltas, - appUnitsPerDevUnit, aRun); + appUnitsPerDevUnit, aRun, aUnmatched, + aString); --stringTailOffset; --numGlyphs; ++glyphRecords; @@ -709,11 +722,13 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun, SetGlyphsForCharacterGroup(glyphRecords + numGlyphs - glyphCount, glyphCount, baselineDeltas ? baselineDeltas + numGlyphs - glyphCount : nsnull, - appUnitsPerDevUnit, aRun); + appUnitsPerDevUnit, aRun, aUnmatched, + aString); } else { SetGlyphsForCharacterGroup(glyphRecords, glyphCount, baselineDeltas, - appUnitsPerDevUnit, aRun); + appUnitsPerDevUnit, aRun, aUnmatched, + aString); glyphRecords += glyphCount; if (baselineDeltas) { baselineDeltas += glyphCount; diff --git a/gfx/thebes/src/gfxFont.cpp b/gfx/thebes/src/gfxFont.cpp index c3fab805c24..304b875d50a 100644 --- a/gfx/thebes/src/gfxFont.cpp +++ b/gfx/thebes/src/gfxFont.cpp @@ -46,6 +46,7 @@ #include "prtypes.h" #include "gfxTypes.h" #include "gfxContext.h" +#include "gfxFontMissingGlyphs.h" #include "cairo.h" #include "gfxFontTest.h" @@ -128,8 +129,25 @@ gfxFont::Draw(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd, break; ++details; } + } else if (glyphData->IsMissing()) { + const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i); + if (details) { + double advance = details->mAdvance; + if (!aDrawToPath) { + gfxPoint pt(ToDeviceUnits(x, devUnitsPerAppUnit), + ToDeviceUnits(y, devUnitsPerAppUnit)); + gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit); + if (isRTL) { + pt.x -= advanceDevUnits; + } + gfxFloat height = GetMetrics().maxAscent; + gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height); + gfxFontMissingGlyphs::DrawMissingGlyph(aContext, glyphRect, details->mGlyphID); + } + x += direction*advance; + } } - // Every other glyph type (including missing glyphs) is ignored + // Every other glyph type is ignored if (aSpacing) { double space = aSpacing[i - aStart].mAfter; if (i + 1 < aEnd) { @@ -175,9 +193,9 @@ gfxFont::Measure(gfxTextRun *aTextRun, ++clusterCount; if (g.IsSimpleGlyph()) { advance += charGlyphs[i].GetSimpleAdvance(); - } else if (g.IsComplexCluster()) { + } else if (g.IsComplexOrMissing()) { const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i); - for (;;) { + while (details) { advance += details->mAdvance; if (details->mIsLastGlyph) break; @@ -653,14 +671,14 @@ gfxTextRun::GetAdjustedSpacing(PRUint32 aStart, PRUint32 aEnd, aSpacing[i - 1 - aStart].mAfter -= clusterWidth; } clusterWidth = glyphData->GetSimpleAdvance(); - } else if (glyphData->IsComplexCluster()) { + } else if (glyphData->IsComplexOrMissing()) { NS_ASSERTION(mDetailedGlyphs, "No details but we have a complex cluster..."); if (i > aStart) { aSpacing[i - 1 - aStart].mAfter -= clusterWidth; } DetailedGlyph *details = mDetailedGlyphs[i]; clusterWidth = 0; - for (;;) { + while (details) { clusterWidth += details->mAdvance; if (details->mIsLastGlyph) break; @@ -1135,10 +1153,10 @@ gfxTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, CompressedGlyph *glyphData = &charGlyphs[i]; if (glyphData->IsSimpleGlyph()) { advance += glyphData->GetSimpleAdvance(); - } else if (glyphData->IsComplexCluster()) { + } else if (glyphData->IsComplexOrMissing()) { NS_ASSERTION(mDetailedGlyphs, "No details but we have a complex cluster..."); DetailedGlyph *details = mDetailedGlyphs[i]; - for (;;) { + while (details) { advance += details->mAdvance; if (details->mIsLastGlyph) break; @@ -1230,10 +1248,9 @@ gfxTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, CompressedGlyph *glyphData = &charGlyphs[i]; if (glyphData->IsSimpleGlyph()) { result += glyphData->GetSimpleAdvance(); - } else if (glyphData->IsComplexCluster()) { - NS_ASSERTION(mDetailedGlyphs, "No details but we have a complex cluster..."); + } else if (glyphData->IsComplexOrMissing()) { DetailedGlyph *details = mDetailedGlyphs[i]; - for (;;) { + while (details) { result += details->mAdvance; if (details->mIsLastGlyph) break; @@ -1307,6 +1324,28 @@ gfxTextRun::CountMissingGlyphs() return count; } +gfxTextRun::DetailedGlyph * +gfxTextRun::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount) +{ + if (!mCharacterGlyphs) + return nsnull; + + if (!mDetailedGlyphs) { + mDetailedGlyphs = new nsAutoArrayPtr[mCharacterCount]; + if (!mDetailedGlyphs) { + mCharacterGlyphs[aIndex].SetMissing(); + return nsnull; + } + } + DetailedGlyph *details = new DetailedGlyph[aCount]; + if (!details) { + mCharacterGlyphs[aIndex].SetMissing(); + return nsnull; + } + mDetailedGlyphs[aIndex] = details; + return details; +} + void gfxTextRun::SetDetailedGlyphs(PRUint32 aIndex, const DetailedGlyph *aGlyphs, PRUint32 aCount) @@ -1314,26 +1353,31 @@ gfxTextRun::SetDetailedGlyphs(PRUint32 aIndex, const DetailedGlyph *aGlyphs, NS_ASSERTION(aCount > 0, "Can't set zero detailed glyphs"); NS_ASSERTION(aGlyphs[aCount - 1].mIsLastGlyph, "Failed to set last glyph flag"); - if (!mCharacterGlyphs) + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, aCount); + if (!details) return; - if (!mDetailedGlyphs) { - mDetailedGlyphs = new nsAutoArrayPtr[mCharacterCount]; - if (!mDetailedGlyphs) { - mCharacterGlyphs[aIndex].SetMissing(); - return; - } - } - DetailedGlyph *details = new DetailedGlyph[aCount]; - if (!details) { - mCharacterGlyphs[aIndex].SetMissing(); - return; - } memcpy(details, aGlyphs, sizeof(DetailedGlyph)*aCount); - - mDetailedGlyphs[aIndex] = details; mCharacterGlyphs[aIndex].SetComplexCluster(); } + +void +gfxTextRun::SetMissingGlyph(PRUint32 aIndex, PRUnichar aChar) +{ + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + if (!details) + return; + + details->mIsLastGlyph = PR_TRUE; + details->mGlyphID = aChar; + GlyphRun *glyphRun = &mGlyphRuns[FindFirstGlyphRunContaining(aIndex)]; + gfxFloat width = PR_MAX(glyphRun->mFont->GetMetrics().aveCharWidth, + gfxFontMissingGlyphs::GetDesiredMinWidth()); + details->mAdvance = PRUint32(width*GetAppUnitsPerDevUnit()); + details->mXOffset = 0; + details->mYOffset = 0; + mCharacterGlyphs[aIndex].SetMissing(); +} void gfxTextRun::RecordSurrogates(const PRUnichar *aString) diff --git a/gfx/thebes/src/gfxFontMissingGlyphs.cpp b/gfx/thebes/src/gfxFontMissingGlyphs.cpp new file mode 100644 index 00000000000..33c58b63dc3 --- /dev/null +++ b/gfx/thebes/src/gfxFontMissingGlyphs.cpp @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Corporation code. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Robert O'Callahan + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "gfxFontMissingGlyphs.h" + +#define CHAR_BITS(b00, b01, b02, b10, b11, b12, b20, b21, b22, b30, b31, b32, b40, b41, b42) \ + ((b00 << 0) | (b01 << 1) | (b02 << 2) | (b10 << 3) | (b11 << 4) | (b12 << 5) | \ + (b20 << 6) | (b21 << 7) | (b22 << 8) | (b30 << 9) | (b31 << 10) | (b32 << 11) | \ + (b40 << 12) | (b41 << 13) | (b42 << 14)) + +static const PRUint16 glyphMicroFont[16] = { + CHAR_BITS(0, 1, 0, + 1, 0, 1, + 1, 0, 1, + 1, 0, 1, + 0, 1, 0), + CHAR_BITS(0, 1, 0, + 0, 1, 0, + 0, 1, 0, + 0, 1, 0, + 0, 1, 0), + CHAR_BITS(1, 1, 1, + 0, 0, 1, + 1, 1, 1, + 1, 0, 0, + 1, 1, 1), + CHAR_BITS(1, 1, 1, + 0, 0, 1, + 1, 1, 1, + 0, 0, 1, + 1, 1, 1), + CHAR_BITS(1, 0, 1, + 1, 0, 1, + 1, 1, 1, + 0, 0, 1, + 0, 0, 1), + CHAR_BITS(1, 1, 1, + 1, 0, 0, + 1, 1, 1, + 0, 0, 1, + 1, 1, 1), + CHAR_BITS(1, 1, 1, + 1, 0, 0, + 1, 1, 1, + 1, 0, 1, + 1, 1, 1), + CHAR_BITS(1, 1, 1, + 0, 0, 1, + 0, 0, 1, + 0, 0, 1, + 0, 0, 1), + CHAR_BITS(1, 1, 1, + 1, 0, 1, + 1, 1, 1, + 1, 0, 1, + 1, 1, 1), + CHAR_BITS(1, 1, 1, + 1, 0, 1, + 1, 1, 1, + 0, 0, 1, + 0, 0, 1), + CHAR_BITS(1, 1, 1, + 1, 0, 1, + 1, 1, 1, + 1, 0, 1, + 1, 0, 1), + CHAR_BITS(1, 1, 0, + 1, 0, 1, + 1, 1, 0, + 1, 0, 1, + 1, 1, 0), + CHAR_BITS(0, 1, 1, + 1, 0, 0, + 1, 0, 0, + 1, 0, 0, + 0, 1, 1), + CHAR_BITS(1, 1, 0, + 1, 0, 1, + 1, 0, 1, + 1, 0, 1, + 1, 1, 0), + CHAR_BITS(1, 1, 1, + 1, 0, 0, + 1, 1, 1, + 1, 0, 0, + 1, 1, 1), + CHAR_BITS(1, 1, 1, + 1, 0, 0, + 1, 1, 1, + 1, 0, 0, + 1, 0, 0) +}; + +/* Parameters that control the rendering of hexboxes. They look like this: + + +---------+ + | | + | HHH HHH | + | HHH HHH | + | HHH HHH | + | HHH HHH | + | HHH HHH | + | | + | HHH HHH | + | HHH HHH | + | HHH HHH | + | HHH HHH | + | HHH HHH | + | | + +---------+ +*/ + +/** Width of a minifont glyph (see above) */ +static const int MINIFONT_WIDTH = 3; +/** Height of a minifont glyph (see above) */ +static const int MINIFONT_HEIGHT = 5; +/** + * Gap between minifont glyphs (both horizontal and vertical) and also + * the minimum desired gap between the box border and the glyphs + */ +static const int HEX_CHAR_GAP = 1; +/** + * The amount of space between the vertical edge of the glyphbox and the + * box border. We make this nonzero so that when multiple missing glyphs + * occur consecutively there's a gap between their rendered boxes. + */ +static const int BOX_HORIZONTAL_INSET = 1; +/** The width of the border */ +static const int BOX_BORDER_WIDTH = 1; +/** + * The scaling factor for the border opacity; this is multiplied by the current + * opacity being used to draw the text. + */ +static const gfxFloat BOX_BORDER_OPACITY = 0.5; +/** + * The minimum desired width for a missing-glyph glyph box. I've laid it out + * like this so you can see what goes where. + */ +static const int MIN_DESIRED_WIDTH = + BOX_HORIZONTAL_INSET + BOX_BORDER_WIDTH + HEX_CHAR_GAP + + MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + + HEX_CHAR_GAP + BOX_BORDER_WIDTH + BOX_HORIZONTAL_INSET; + +/** + * Draw a single hex character using the current color. A nice way to do this + * would be to fill in an A8 image surface and then use it as a mask + * to paint the current color. Tragically this doesn't currently work with the + * Quartz cairo backend which doesn't generally support masking with surfaces. + * So for now we just paint a bunch of rectangles... + */ +static void +DrawHexChar(gfxContext *aContext, const gfxPoint& aPt, PRUint32 aDigit) +{ + aContext->NewPath(); + PRUint32 glyphBits = glyphMicroFont[aDigit]; + int x, y; + for (y = 0; y < MINIFONT_HEIGHT; ++y) { + for (x = 0; x < MINIFONT_WIDTH; ++x) { + if (glyphBits & 1) { + aContext->Rectangle(gfxRect(x, y, 1, 1) + aPt, PR_TRUE); + } + glyphBits >>= 1; + } + } + aContext->Fill(); +} + +void +gfxFontMissingGlyphs::DrawMissingGlyph(gfxContext *aContext, const gfxRect& aRect, + PRUnichar aChar) +{ + aContext->Save(); + + gfxRGBA currentColor; + if (!aContext->GetColor(currentColor)) { + // We're currently drawing with some kind of pattern... Just draw + // the missing-glyph data in black. + currentColor = gfxRGBA(0,0,0,1); + } + + // Stroke a rectangle so that the stroke's left edge is inset one pixel + // from the left edge of the glyph box and the stroke's right edge + // is inset one pixel from the right edge of the glyph box. + gfxFloat halfBorderWidth = BOX_BORDER_WIDTH/2.0; + gfxFloat borderLeft = aRect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth; + gfxFloat borderRight = aRect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth; + gfxRect borderStrokeRect(borderLeft, aRect.Y() + halfBorderWidth, + borderRight - borderLeft, aRect.Height() - 2*halfBorderWidth); + if (!borderStrokeRect.IsEmpty()) { + aContext->SetLineWidth(BOX_BORDER_WIDTH); + aContext->SetDash(gfxContext::gfxLineSolid); + aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE); + aContext->SetLineJoin(gfxContext::LINE_JOIN_MITER); + gfxRGBA color = currentColor; + color.a *= BOX_BORDER_OPACITY; + aContext->SetColor(color); + aContext->NewPath(); + aContext->Rectangle(borderStrokeRect); + aContext->Stroke(); + } + + if (aRect.Width() >= 2*MINIFONT_WIDTH + HEX_CHAR_GAP && + aRect.Height() >= 2*MINIFONT_HEIGHT + HEX_CHAR_GAP) { + aContext->SetColor(currentColor); + gfxPoint center(aRect.X() + aRect.Width()/2, + aRect.Y() + aRect.Height()/2); + gfxFloat halfGap = HEX_CHAR_GAP/2.0; + gfxFloat left = -(MINIFONT_WIDTH + halfGap); + gfxFloat top = -(MINIFONT_HEIGHT + halfGap); + DrawHexChar(aContext, + center + gfxPoint(left, top), (aChar >> 12) & 0xF); + DrawHexChar(aContext, + center + gfxPoint(halfGap, top), (aChar >> 8) & 0xF); + DrawHexChar(aContext, + center + gfxPoint(left, halfGap), (aChar >> 4) & 0xF); + DrawHexChar(aContext, + center + gfxPoint(halfGap, halfGap), aChar & 0xF); + } + + aContext->Restore(); +} + +gfxFloat +gfxFontMissingGlyphs::GetDesiredMinWidth() +{ + return MIN_DESIRED_WIDTH; +} diff --git a/gfx/thebes/src/gfxFontMissingGlyphs.h b/gfx/thebes/src/gfxFontMissingGlyphs.h new file mode 100644 index 00000000000..9904bb954f6 --- /dev/null +++ b/gfx/thebes/src/gfxFontMissingGlyphs.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Corporation code. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Robert O'Callahan + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef GFX_FONTMISSINGGLYPHS_H +#define GFX_FONTMISSINGGLYPHS_H + +#include "prtypes.h" +#include "gfxTypes.h" +#include "gfxContext.h" +#include "gfxRect.h" + +/** + * This class should not be instantiated. It's just a container + * for some helper functions. + */ +class THEBES_API gfxFontMissingGlyphs { +public: + /** + * Draw hexboxes for a missing glyph. + * @param aContext the context to draw to + * @param aRect the glyph-box for the glyph that is missing + * @param aChar the UTF16 codepoint for the character + */ + static void DrawMissingGlyph(gfxContext *aContext, const gfxRect& aRect, + PRUnichar aChar); + /** + * @return the desired minimum width for a glyph-box that will allow + * the hexboxes to be drawn reasonably. + */ + static gfxFloat GetDesiredMinWidth(); +}; + +#endif diff --git a/gfx/thebes/src/gfxPangoFonts.cpp b/gfx/thebes/src/gfxPangoFonts.cpp index bcc5ff94654..11e2046113a 100644 --- a/gfx/thebes/src/gfxPangoFonts.cpp +++ b/gfx/thebes/src/gfxPangoFonts.cpp @@ -940,8 +940,23 @@ ConvertPangoToAppUnits(PRInt32 aCoordinate, PRUint32 aAppUnitsPerDevUnit) return PRInt32(v); } +static void +SetMissingGlyphForUCS4(gfxTextRun *aTextRun, PRUint32 aIndex, gunichar aCh) +{ + if (aCh < 0x10000) { + aTextRun->SetMissingGlyph(aIndex, PRUnichar(aCh)); + return; + } + + // Display non-BMP characters as a surrogate pair + aTextRun->SetMissingGlyph(aIndex, H_SURROGATE(aCh)); + if (aIndex + 1 < aTextRun->GetLength()) { + aTextRun->SetMissingGlyph(aIndex + 1, L_SURROGATE(aCh)); + } +} + nsresult -gfxPangoFontGroup::SetGlyphs(gfxTextRun* aTextRun, +gfxPangoFontGroup::SetGlyphs(gfxTextRun *aTextRun, const gchar *aUTF8, PRUint32 aUTF8Length, PRUint32 *aUTF16Offset, PangoGlyphString *aGlyphs, PangoGlyphUnit aOverrideSpaceWidth, @@ -966,9 +981,11 @@ gfxPangoFontGroup::SetGlyphs(gfxTextRun* aTextRun, NS_ERROR("Someone has added too many glyphs!"); break; } - if (aUTF8[index] == 0) { - // treat this null byte as a missing glyph - aTextRun->SetCharacterGlyph(utf16Offset, g.SetMissing()); + gunichar ch = g_utf8_get_char(aUTF8 + index); + if (ch == 0) { + // treat this null byte as a missing glyph. Pango doesn't create + // glyphs for these, not even missing-glyph glyphIDs. + aTextRun->SetMissingGlyph(utf16Offset, 0); } else if (glyphCount == numGlyphs || PRUint32(logClusters[glyphIndex]) > index) { // No glyphs for this cluster, and it's not a null byte. @@ -1018,7 +1035,7 @@ gfxPangoFontGroup::SetGlyphs(gfxTextRun* aTextRun, } else if (haveMissingGlyph) { // Note that missing-glyph IDs are not simple glyph IDs, so we'll // always get here when a glyph is missing - aTextRun->SetCharacterGlyph(utf16Offset, g.SetMissing()); + SetMissingGlyphForUCS4(aTextRun, utf16Offset, ch); } else { if (detailedGlyphs.Length() < glyphClusterCount) { if (!detailedGlyphs.AppendElements(glyphClusterCount - detailedGlyphs.Length())) @@ -1044,7 +1061,6 @@ gfxPangoFontGroup::SetGlyphs(gfxTextRun* aTextRun, } } - gunichar ch = g_utf8_get_char(aUTF8 + index); ++utf16Offset; NS_ASSERTION(!IS_SURROGATE(ch), "surrogates should not appear in UTF8"); if (ch >= 0x10000) { @@ -1071,13 +1087,16 @@ gfxPangoFontGroup::CreateGlyphRunsXft(gfxTextRun *aTextRun, gfxTextRun::CompressedGlyph g; const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); + aTextRun->AddGlyphRun(font, 0); + while (p < aUTF8 + aUTF8Length) { gunichar ch = g_utf8_get_char(p); p = g_utf8_next_char(p); if (ch == 0) { - // treat this null byte as a missing glyph - aTextRun->SetCharacterGlyph(utf16Offset, g.SetMissing()); + // treat this null byte as a missing glyph. Pango + // doesn't create glyphs for these, not even missing-glyphs. + aTextRun->SetMissingGlyph(utf16Offset, 0); } else { FT_UInt glyph = XftCharIndex(dpy, xfont, ch); XGlyphInfo info; @@ -1095,7 +1114,7 @@ gfxPangoFontGroup::CreateGlyphRunsXft(gfxTextRun *aTextRun, } else if (IS_MISSING_GLYPH(glyph)) { // Note that missing-glyph IDs are not simple glyph IDs, so we'll // always get here when a glyph is missing - aTextRun->SetCharacterGlyph(utf16Offset, g.SetMissing()); + SetMissingGlyphForUCS4(aTextRun, utf16Offset, ch); } else { gfxTextRun::DetailedGlyph details; details.mIsLastGlyph = PR_TRUE; @@ -1117,7 +1136,6 @@ gfxPangoFontGroup::CreateGlyphRunsXft(gfxTextRun *aTextRun, ++utf16Offset; } - aTextRun->AddGlyphRun(font, 0); } #endif @@ -1179,14 +1197,12 @@ gfxPangoFontGroup::CreateGlyphRunsFast(gfxTextRun *aTextRun, pango_shape(aUTF8, aUTF8Length, &analysis, glyphString); PRUint32 utf16Offset = 0; - nsresult rv = SetGlyphs(aTextRun, aUTF8, aUTF8Length, &utf16Offset, glyphString, 0, PR_TRUE); - - pango_glyph_string_free(glyphString); - + nsresult rv = aTextRun->AddGlyphRun(font, 0); if (NS_FAILED(rv)) - return rv; - - return aTextRun->AddGlyphRun(font, 0); + return rv; + rv = SetGlyphs(aTextRun, aUTF8, aUTF8Length, &utf16Offset, glyphString, 0, PR_TRUE); + pango_glyph_string_free(glyphString); + return rv; } class FontSelector @@ -1258,11 +1274,14 @@ public: mItem->analysis.font = tmpFont; } + nsresult rv = mTextRun->AddGlyphRun(aFont, incomingUTF16Offset); + if (NS_FAILED(rv)) + return rv; mGroup->SetGlyphs(mTextRun, mString + mSegmentOffset, aUTF8Length, &mUTF16Offset, aGlyphs, mSpaceWidth, PR_FALSE); mSegmentOffset += aUTF8Length; - return mTextRun->AddGlyphRun(aFont, incomingUTF16Offset); + return NS_OK; } private: diff --git a/gfx/thebes/src/gfxWindowsFonts.cpp b/gfx/thebes/src/gfxWindowsFonts.cpp index 399093781d0..7559967bba9 100644 --- a/gfx/thebes/src/gfxWindowsFonts.cpp +++ b/gfx/thebes/src/gfxWindowsFonts.cpp @@ -1132,6 +1132,8 @@ public: PRUint32 offsetInRun = mScriptItem->iCharPos; SetupClusterBoundaries(aRun, offsetInRun); + aRun->AddGlyphRun(GetCurrentFont(), offsetInRun); + // XXX We should store this in the item and only fetch it once SCRIPT_FONTPROPERTIES sfp; ScriptFontProperties(&sfp); @@ -1168,7 +1170,7 @@ public: PRInt32 advance = mAdvances[k]*appUnitsPerDevUnit; WORD glyph = mGlyphs[k]; if (missing) { - aRun->SetCharacterGlyph(runOffset, g.SetMissing()); + aRun->SetMissingGlyph(runOffset, mString[offset]); } else if (glyphCount == 1 && advance >= 0 && mOffsets[k].dv == 0 && mOffsets[k].du == 0 && gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && @@ -1193,8 +1195,6 @@ public: } ++offset; } - - aRun->AddGlyphRun(GetCurrentFont(), offsetInRun); } gfxWindowsFont *GetNextFont() {