1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Novell code.
16 : *
17 : * The Initial Developer of the Original Code is Novell Corporation.
18 : * Portions created by the Initial Developer are Copyright (C) 2006
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * robert@ocallahan.org
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsTextRunTransformations.h"
39 :
40 : #include "nsTextFrameUtils.h"
41 : #include "gfxSkipChars.h"
42 :
43 : #include "nsStyleConsts.h"
44 : #include "nsStyleContext.h"
45 : #include "gfxContext.h"
46 : #include "nsContentUtils.h"
47 : #include "nsUnicharUtils.h"
48 :
49 : #define SZLIG 0x00DF
50 :
51 : nsTransformedTextRun *
52 0 : nsTransformedTextRun::Create(const gfxTextRunFactory::Parameters* aParams,
53 : nsTransformingTextRunFactory* aFactory,
54 : gfxFontGroup* aFontGroup,
55 : const PRUnichar* aString, PRUint32 aLength,
56 : const PRUint32 aFlags, nsStyleContext** aStyles,
57 : bool aOwnsFactory)
58 : {
59 0 : NS_ASSERTION(!(aFlags & gfxTextRunFactory::TEXT_IS_8BIT),
60 : "didn't expect text to be marked as 8-bit here");
61 :
62 0 : void *storage = AllocateStorageForTextRun(sizeof(nsTransformedTextRun), aLength);
63 0 : if (!storage) {
64 0 : return nsnull;
65 : }
66 :
67 : return new (storage) nsTransformedTextRun(aParams, aFactory, aFontGroup,
68 : aString, aLength,
69 0 : aFlags, aStyles, aOwnsFactory);
70 : }
71 :
72 : void
73 0 : nsTransformedTextRun::SetCapitalization(PRUint32 aStart, PRUint32 aLength,
74 : bool* aCapitalization,
75 : gfxContext* aRefContext)
76 : {
77 0 : if (mCapitalize.IsEmpty()) {
78 0 : if (!mCapitalize.AppendElements(GetLength()))
79 0 : return;
80 0 : memset(mCapitalize.Elements(), 0, GetLength()*sizeof(bool));
81 : }
82 0 : memcpy(mCapitalize.Elements() + aStart, aCapitalization, aLength*sizeof(bool));
83 0 : mNeedsRebuild = true;
84 : }
85 :
86 : bool
87 0 : nsTransformedTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
88 : PRUint8* aBreakBefore,
89 : gfxContext* aRefContext)
90 : {
91 : bool changed = gfxTextRun::SetPotentialLineBreaks(aStart, aLength,
92 0 : aBreakBefore, aRefContext);
93 0 : if (changed) {
94 0 : mNeedsRebuild = true;
95 : }
96 0 : return changed;
97 : }
98 :
99 : size_t
100 0 : nsTransformedTextRun::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf)
101 : {
102 0 : size_t total = gfxTextRun::SizeOfExcludingThis(aMallocSizeOf);
103 0 : total += mStyles.SizeOfExcludingThis(aMallocSizeOf);
104 0 : total += mCapitalize.SizeOfExcludingThis(aMallocSizeOf);
105 0 : if (mOwnsFactory) {
106 0 : total += aMallocSizeOf(mFactory);
107 : }
108 0 : return total;
109 : }
110 :
111 : size_t
112 0 : nsTransformedTextRun::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf)
113 : {
114 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
115 : }
116 :
117 : nsTransformedTextRun*
118 0 : nsTransformingTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
119 : const gfxTextRunFactory::Parameters* aParams,
120 : gfxFontGroup* aFontGroup, PRUint32 aFlags,
121 : nsStyleContext** aStyles, bool aOwnsFactory)
122 : {
123 : return nsTransformedTextRun::Create(aParams, this, aFontGroup,
124 0 : aString, aLength, aFlags, aStyles, aOwnsFactory);
125 : }
126 :
127 : nsTransformedTextRun*
128 0 : nsTransformingTextRunFactory::MakeTextRun(const PRUint8* aString, PRUint32 aLength,
129 : const gfxTextRunFactory::Parameters* aParams,
130 : gfxFontGroup* aFontGroup, PRUint32 aFlags,
131 : nsStyleContext** aStyles, bool aOwnsFactory)
132 : {
133 : // We'll only have a Unicode code path to minimize the amount of code needed
134 : // for these rarely used features
135 0 : NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString), aLength);
136 : return MakeTextRun(unicodeString.get(), aLength, aParams, aFontGroup,
137 : aFlags & ~(gfxFontGroup::TEXT_IS_PERSISTENT | gfxFontGroup::TEXT_IS_8BIT),
138 0 : aStyles, aOwnsFactory);
139 : }
140 :
141 : /**
142 : * Copy a given textrun, but merge certain characters into a single logical
143 : * character. Glyphs for a character are added to the glyph list for the previous
144 : * character and then the merged character is eliminated. Visually the results
145 : * are identical.
146 : *
147 : * This is used for text-transform:uppercase when we encounter a SZLIG,
148 : * whose uppercase form is "SS".
149 : *
150 : * This function is unable to merge characters when they occur in different
151 : * glyph runs. It's hard to see how this could happen, but if it does, we just
152 : * discard the characters-to-merge.
153 : *
154 : * For simplicity, this produces a textrun containing all DetailedGlyphs,
155 : * no simple glyphs. So don't call it unless you really have merging to do.
156 : *
157 : * @param aCharsToMerge when aCharsToMerge[i] is true, this character is
158 : * merged into the previous character
159 : */
160 : static void
161 0 : MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
162 : bool* aCharsToMerge)
163 : {
164 0 : aDest->ResetGlyphRuns();
165 :
166 0 : gfxTextRun::GlyphRunIterator iter(aSrc, 0, aSrc->GetLength());
167 0 : PRUint32 offset = 0;
168 0 : nsAutoTArray<gfxTextRun::DetailedGlyph,2> glyphs;
169 0 : while (iter.NextRun()) {
170 0 : gfxTextRun::GlyphRun* run = iter.GetGlyphRun();
171 : nsresult rv = aDest->AddGlyphRun(run->mFont, run->mMatchType,
172 0 : offset, false);
173 0 : if (NS_FAILED(rv))
174 : return;
175 :
176 0 : bool anyMissing = false;
177 0 : PRUint32 mergeRunStart = iter.GetStringStart();
178 : PRUint32 k;
179 0 : for (k = iter.GetStringStart(); k < iter.GetStringEnd(); ++k) {
180 0 : gfxTextRun::CompressedGlyph g = aSrc->GetCharacterGlyphs()[k];
181 0 : if (g.IsSimpleGlyph()) {
182 0 : if (!anyMissing) {
183 : gfxTextRun::DetailedGlyph details;
184 0 : details.mGlyphID = g.GetSimpleGlyph();
185 0 : details.mAdvance = g.GetSimpleAdvance();
186 0 : details.mXOffset = 0;
187 0 : details.mYOffset = 0;
188 0 : glyphs.AppendElement(details);
189 : }
190 : } else {
191 0 : if (g.IsMissing()) {
192 0 : anyMissing = true;
193 0 : glyphs.Clear();
194 : }
195 0 : if (g.GetGlyphCount() > 0) {
196 0 : glyphs.AppendElements(aSrc->GetDetailedGlyphs(k), g.GetGlyphCount());
197 : }
198 : }
199 :
200 : // We could teach this method to handle merging of characters that aren't
201 : // cluster starts or ligature group starts, but this is really only used
202 : // to merge S's (uppercase ß), so it's not worth it.
203 :
204 0 : if (k + 1 < iter.GetStringEnd() && aCharsToMerge[k + 1]) {
205 0 : NS_ASSERTION(g.IsClusterStart() && g.IsLigatureGroupStart(),
206 : "Don't know how to merge this stuff");
207 0 : continue;
208 : }
209 :
210 0 : NS_ASSERTION(mergeRunStart == k ||
211 : (g.IsClusterStart() && g.IsLigatureGroupStart()),
212 : "Don't know how to merge this stuff");
213 :
214 : // If the start of the merge run is actually a character that should
215 : // have been merged with the previous character (this can happen
216 : // if there's a font change in the middle of a szlig, for example),
217 : // just discard the entire merge run. See comment at start of this
218 : // function.
219 0 : if (!aCharsToMerge[mergeRunStart]) {
220 0 : if (anyMissing) {
221 0 : g.SetMissing(glyphs.Length());
222 : } else {
223 0 : g.SetComplex(true, true, glyphs.Length());
224 : }
225 0 : aDest->SetGlyphs(offset, g, glyphs.Elements());
226 0 : ++offset;
227 : }
228 :
229 0 : glyphs.Clear();
230 0 : anyMissing = false;
231 0 : mergeRunStart = k + 1;
232 : }
233 0 : NS_ASSERTION(glyphs.Length() == 0,
234 : "Leftover glyphs, don't request merging of the last character with its next!");
235 : }
236 0 : NS_ASSERTION(offset == aDest->GetLength(), "Bad offset calculations");
237 : }
238 :
239 : static gfxTextRunFactory::Parameters
240 0 : GetParametersForInner(nsTransformedTextRun* aTextRun, PRUint32* aFlags,
241 : gfxContext* aRefContext)
242 : {
243 : gfxTextRunFactory::Parameters params =
244 : { aRefContext, nsnull, nsnull,
245 0 : nsnull, 0, aTextRun->GetAppUnitsPerDevUnit()
246 0 : };
247 0 : *aFlags = aTextRun->GetFlags() & ~gfxFontGroup::TEXT_IS_PERSISTENT;
248 : return params;
249 : }
250 :
251 : void
252 0 : nsFontVariantTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
253 : gfxContext* aRefContext)
254 : {
255 0 : gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
256 0 : gfxFontStyle fontStyle = *fontGroup->GetStyle();
257 0 : fontStyle.size *= 0.8;
258 0 : nsRefPtr<gfxFontGroup> smallFont = fontGroup->Copy(&fontStyle);
259 0 : if (!smallFont)
260 : return;
261 :
262 : PRUint32 flags;
263 : gfxTextRunFactory::Parameters innerParams =
264 0 : GetParametersForInner(aTextRun, &flags, aRefContext);
265 :
266 0 : PRUint32 length = aTextRun->GetLength();
267 0 : const PRUnichar* str = aTextRun->mString.BeginReading();
268 0 : nsRefPtr<nsStyleContext>* styles = aTextRun->mStyles.Elements();
269 : // Create a textrun so we can check cluster-start properties
270 0 : nsAutoPtr<gfxTextRun> inner(fontGroup->MakeTextRun(str, length, &innerParams, flags));
271 0 : if (!inner.get())
272 : return;
273 :
274 0 : nsCaseTransformTextRunFactory uppercaseFactory(nsnull, true);
275 :
276 0 : aTextRun->ResetGlyphRuns();
277 :
278 0 : PRUint32 runStart = 0;
279 0 : bool runIsLowercase = false;
280 0 : nsAutoTArray<nsStyleContext*,50> styleArray;
281 0 : nsAutoTArray<PRUint8,50> canBreakBeforeArray;
282 :
283 : PRUint32 i;
284 0 : for (i = 0; i <= length; ++i) {
285 0 : bool isLowercase = false;
286 0 : if (i < length) {
287 : // Characters that aren't the start of a cluster are ignored here. They
288 : // get added to whatever lowercase/non-lowercase run we're in.
289 0 : if (!inner->IsClusterStart(i)) {
290 0 : isLowercase = runIsLowercase;
291 : } else {
292 0 : if (styles[i]->GetStyleFont()->mFont.variant == NS_STYLE_FONT_VARIANT_SMALL_CAPS) {
293 0 : PRUnichar ch = str[i];
294 : PRUnichar ch2;
295 0 : ch2 = ToUpperCase(ch);
296 0 : isLowercase = ch != ch2 || ch == SZLIG;
297 : } else {
298 : // Don't transform the character! I.e., pretend that it's not lowercase
299 : }
300 : }
301 : }
302 :
303 0 : if ((i == length || runIsLowercase != isLowercase) && runStart < i) {
304 0 : nsAutoPtr<nsTransformedTextRun> transformedChild;
305 0 : nsAutoPtr<gfxTextRun> cachedChild;
306 : gfxTextRun* child;
307 :
308 0 : if (runIsLowercase) {
309 : transformedChild = uppercaseFactory.MakeTextRun(str + runStart, i - runStart,
310 0 : &innerParams, smallFont, flags, styleArray.Elements(), false);
311 0 : child = transformedChild;
312 : } else {
313 : cachedChild =
314 : fontGroup->MakeTextRun(str + runStart, i - runStart,
315 0 : &innerParams, flags);
316 0 : child = cachedChild.get();
317 : }
318 0 : if (!child)
319 : return;
320 : // Copy potential linebreaks into child so they're preserved
321 : // (and also child will be shaped appropriately)
322 0 : NS_ASSERTION(canBreakBeforeArray.Length() == i - runStart,
323 : "lost some break-before values?");
324 : child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
325 0 : canBreakBeforeArray.Elements(), aRefContext);
326 0 : if (transformedChild) {
327 0 : transformedChild->FinishSettingProperties(aRefContext);
328 : }
329 0 : aTextRun->CopyGlyphDataFrom(child, 0, child->GetLength(), runStart);
330 :
331 0 : runStart = i;
332 0 : styleArray.Clear();
333 0 : canBreakBeforeArray.Clear();
334 : }
335 :
336 0 : if (i < length) {
337 0 : runIsLowercase = isLowercase;
338 0 : styleArray.AppendElement(styles[i]);
339 0 : canBreakBeforeArray.AppendElement(aTextRun->CanBreakLineBefore(i));
340 : }
341 : }
342 : }
343 :
344 : void
345 0 : nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
346 : gfxContext* aRefContext)
347 : {
348 0 : PRUint32 length = aTextRun->GetLength();
349 0 : const PRUnichar* str = aTextRun->mString.BeginReading();
350 0 : nsRefPtr<nsStyleContext>* styles = aTextRun->mStyles.Elements();
351 :
352 0 : nsAutoString convertedString;
353 0 : nsAutoTArray<bool,50> charsToMergeArray;
354 0 : nsAutoTArray<nsStyleContext*,50> styleArray;
355 0 : nsAutoTArray<PRUint8,50> canBreakBeforeArray;
356 0 : PRUint32 extraCharsCount = 0;
357 :
358 : PRUint32 i;
359 0 : for (i = 0; i < length; ++i) {
360 0 : PRUnichar ch = str[i];
361 :
362 0 : charsToMergeArray.AppendElement(false);
363 0 : styleArray.AppendElement(styles[i]);
364 0 : canBreakBeforeArray.AppendElement(aTextRun->CanBreakLineBefore(i));
365 :
366 : PRUint8 style = mAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE
367 0 : : styles[i]->GetStyleText()->mTextTransform;
368 0 : bool extraChar = false;
369 :
370 0 : switch (style) {
371 : case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
372 0 : ch = ToLowerCase(ch);
373 0 : break;
374 : case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
375 0 : if (ch == SZLIG) {
376 0 : convertedString.Append('S');
377 0 : extraChar = true;
378 0 : ch = 'S';
379 : } else {
380 0 : ch = ToUpperCase(ch);
381 : }
382 0 : break;
383 : case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
384 0 : if (i < aTextRun->mCapitalize.Length() && aTextRun->mCapitalize[i]) {
385 0 : if (ch == SZLIG) {
386 0 : convertedString.Append('S');
387 0 : extraChar = true;
388 0 : ch = 'S';
389 : } else {
390 0 : ch = ToTitleCase(ch);
391 : }
392 : }
393 0 : break;
394 : default:
395 0 : break;
396 : }
397 :
398 0 : convertedString.Append(ch);
399 0 : if (extraChar) {
400 0 : ++extraCharsCount;
401 0 : charsToMergeArray.AppendElement(true);
402 0 : styleArray.AppendElement(styles[i]);
403 0 : canBreakBeforeArray.AppendElement(false);
404 : }
405 : }
406 :
407 : PRUint32 flags;
408 : gfxTextRunFactory::Parameters innerParams =
409 0 : GetParametersForInner(aTextRun, &flags, aRefContext);
410 0 : gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
411 :
412 0 : nsAutoPtr<nsTransformedTextRun> transformedChild;
413 0 : nsAutoPtr<gfxTextRun> cachedChild;
414 : gfxTextRun* child;
415 :
416 0 : if (mInnerTransformingTextRunFactory) {
417 : transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
418 : convertedString.BeginReading(), convertedString.Length(),
419 0 : &innerParams, fontGroup, flags, styleArray.Elements(), false);
420 0 : child = transformedChild.get();
421 : } else {
422 : cachedChild = fontGroup->MakeTextRun(
423 : convertedString.BeginReading(), convertedString.Length(),
424 0 : &innerParams, flags);
425 0 : child = cachedChild.get();
426 : }
427 0 : if (!child)
428 : return;
429 : // Copy potential linebreaks into child so they're preserved
430 : // (and also child will be shaped appropriately)
431 0 : NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
432 : "Dropped characters or break-before values somewhere!");
433 : child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
434 0 : canBreakBeforeArray.Elements(), aRefContext);
435 0 : if (transformedChild) {
436 0 : transformedChild->FinishSettingProperties(aRefContext);
437 : }
438 :
439 0 : if (extraCharsCount > 0) {
440 : // Now merge multiple characters into one multi-glyph character as required
441 0 : MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements());
442 : } else {
443 : // No merging to do, so just copy; this produces a more optimized textrun.
444 : // We can't steal the data because the child may be cached and stealing
445 : // the data would break the cache.
446 0 : aTextRun->ResetGlyphRuns();
447 0 : aTextRun->CopyGlyphDataFrom(child, 0, child->GetLength(), 0);
448 : }
449 : }
|