/* -*- 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 Foundation code. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bas Schouten * * 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 "gfxDWriteShaper.h" #include "gfxWindowsPlatform.h" #include #include "gfxDWriteTextAnalysis.h" #include "nsCRT.h" #define MAX_RANGE_LENGTH 25000 PRBool gfxDWriteShaper::InitTextRun(gfxContext *aContext, gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32 aRunStart, PRUint32 aRunLength, PRInt32 aRunScript) { HRESULT hr; // TODO: Handle TEST_DISABLE_OPTIONAL_LIGATURES DWRITE_READING_DIRECTION readingDirection = aTextRun->IsRightToLeft() ? DWRITE_READING_DIRECTION_RIGHT_TO_LEFT : DWRITE_READING_DIRECTION_LEFT_TO_RIGHT; gfxDWriteFont *font = static_cast(mFont); gfxTextRun::CompressedGlyph g; nsRefPtr analyzer; hr = gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()-> CreateTextAnalyzer(getter_AddRefs(analyzer)); if (FAILED(hr)) { return PR_FALSE; } /** * There's an internal 16-bit limit on some things inside the analyzer. * to be on the safe side here we split up any ranges over MAX_RANGE_LENGTH * characters. * TODO: Figure out what exactly is going on, and what is a safe number, and * why. */ PRBool result = PR_TRUE; UINT32 rangeOffset = 0; while (rangeOffset < aRunLength) { PRUint32 rangeLen = NS_MIN(aRunLength - rangeOffset, MAX_RANGE_LENGTH); if (rangeOffset + rangeLen < aRunLength) { // Iterate backwards to find a decent place to break shaping: // look for a whitespace char, and if that's not found then // at least a char where IsClusterStart is true. // However, we never iterate back further than half-way; // if a decent break is not found by then, we just chop anyway // at the original range length. PRUint32 adjRangeLen = 0; const PRUnichar *rangeStr = aString + aRunStart + rangeOffset; for (PRUint32 i = rangeLen; i > MAX_RANGE_LENGTH / 2; i--) { if (nsCRT::IsAsciiSpace(rangeStr[i])) { adjRangeLen = i; break; } if (adjRangeLen == 0 && aTextRun->IsClusterStart(aRunStart + rangeOffset + i)) { adjRangeLen = i; } } if (adjRangeLen != 0) { rangeLen = adjRangeLen; } } gfxTextRange range(aRunStart + rangeOffset, aRunStart + rangeOffset + rangeLen); rangeOffset += rangeLen; TextAnalysis analysis(aString + range.start, range.Length(), NULL, readingDirection); TextAnalysis::Run *runHead; DWRITE_LINE_BREAKPOINT *linebreaks; hr = analysis.GenerateResults(analyzer, &runHead, &linebreaks); if (FAILED(hr)) { NS_WARNING("Analyzer failed to generate results."); result = PR_FALSE; break; } PRUint32 appUnitsPerDevPixel = aTextRun->GetAppUnitsPerDevUnit(); UINT32 maxGlyphs = 0; trymoreglyphs: if ((PR_UINT32_MAX - 3 * range.Length() / 2 + 16) < maxGlyphs) { // This isn't going to work, we're going to cross the UINT32 upper // limit. Next range it is. continue; } maxGlyphs += 3 * range.Length() / 2 + 16; nsAutoTArray clusters; nsAutoTArray indices; nsAutoTArray textProperties; nsAutoTArray glyphProperties; if (!clusters.SetLength(range.Length()) || !indices.SetLength(maxGlyphs) || !textProperties.SetLength(maxGlyphs) || !glyphProperties.SetLength(maxGlyphs)) { continue; } UINT32 actualGlyphs; hr = analyzer->GetGlyphs(aString + range.start, range.Length(), font->GetFontFace(), FALSE, readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT, &runHead->mScript, NULL, NULL, NULL, NULL, 0, maxGlyphs, clusters.Elements(), textProperties.Elements(), indices.Elements(), glyphProperties.Elements(), &actualGlyphs); if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { // We increase the amount of glyphs and try again. goto trymoreglyphs; } if (FAILED(hr)) { NS_WARNING("Analyzer failed to get glyphs."); result = PR_FALSE; break; } WORD gID = indices[0]; nsAutoTArray advances; nsAutoTArray glyphOffsets; if (!advances.SetLength(actualGlyphs) || !glyphOffsets.SetLength(actualGlyphs)) { continue; } hr = analyzer->GetGlyphPlacements(aString + range.start, clusters.Elements(), textProperties.Elements(), range.Length(), indices.Elements(), glyphProperties.Elements(), actualGlyphs, font->GetFontFace(), font->GetAdjustedSize(), FALSE, FALSE, &runHead->mScript, NULL, NULL, NULL, 0, advances.Elements(), glyphOffsets.Elements()); if (FAILED(hr)) { NS_WARNING("Analyzer failed to get glyph placements."); result = PR_FALSE; break; } nsAutoTArray detailedGlyphs; for (unsigned int c = 0; c < range.Length(); c++) { PRUint32 k = clusters[c]; PRUint32 absC = range.start + c; if (c > 0 && k == clusters[c - 1]) { g.SetComplex(aTextRun->IsClusterStart(absC), PR_FALSE, 0); aTextRun->SetGlyphs(absC, g, nsnull); // This is a cluster continuation. No glyph here. continue; } // Count glyphs for this character PRUint32 glyphCount = actualGlyphs - k; PRUint32 nextClusterOffset; for (nextClusterOffset = c + 1; nextClusterOffset < range.Length(); ++nextClusterOffset) { if (clusters[nextClusterOffset] > k) { glyphCount = clusters[nextClusterOffset] - k; break; } } PRInt32 advance = (PRInt32)(advances[k] * appUnitsPerDevPixel); if (glyphCount == 1 && advance >= 0 && glyphOffsets[k].advanceOffset == 0 && glyphOffsets[k].ascenderOffset == 0 && aTextRun->IsClusterStart(absC) && gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && gfxTextRun::CompressedGlyph::IsSimpleGlyphID(indices[k])) { aTextRun->SetSimpleGlyph(absC, g.SetSimpleGlyph(advance, indices[k])); } else { if (detailedGlyphs.Length() < glyphCount) { if (!detailedGlyphs.AppendElements( glyphCount - detailedGlyphs.Length())) { continue; } } float totalAdvance = 0; for (unsigned int z = 0; z < glyphCount; z++) { detailedGlyphs[z].mGlyphID = indices[k + z]; detailedGlyphs[z].mAdvance = (PRInt32)(advances[k + z] * appUnitsPerDevPixel); if (readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT) { detailedGlyphs[z].mXOffset = (totalAdvance + glyphOffsets[k + z].advanceOffset) * appUnitsPerDevPixel; } else { detailedGlyphs[z].mXOffset = glyphOffsets[k + z].advanceOffset * appUnitsPerDevPixel; } detailedGlyphs[z].mYOffset = -glyphOffsets[k + z].ascenderOffset * appUnitsPerDevPixel; totalAdvance += advances[k + z]; } aTextRun->SetGlyphs( absC, g.SetComplex(aTextRun->IsClusterStart(absC), PR_TRUE, glyphCount), detailedGlyphs.Elements()); } } } return result; }