1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsTextRunTransformations.h"
8 #include "mozilla/MemoryReporting.h"
10 #include "nsGkAtoms.h"
11 #include "nsStyleConsts.h"
12 #include "nsStyleContext.h"
13 #include "nsUnicharUtils.h"
14 #include "nsUnicodeProperties.h"
15 #include "nsSpecialCasingData.h"
16 #include "mozilla/gfx/2D.h"
17 #include "nsTextFrameUtils.h"
18 #include "nsIPersistentProperties2.h"
19 #include "nsNetUtil.h"
20 #include "GreekCasing.h"
21 #include "IrishCasing.h"
23 // Unicode characters needing special casing treatment in tr/az languages
24 #define LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE 0x0130
25 #define LATIN_SMALL_LETTER_DOTLESS_I 0x0131
27 // Greek sigma needs custom handling for the lowercase transform; for details
28 // see comments under "case NS_STYLE_TEXT_TRANSFORM_LOWERCASE" within
29 // nsCaseTransformTextRunFactory::RebuildTextRun(), and bug 740120.
30 #define GREEK_CAPITAL_LETTER_SIGMA 0x03A3
31 #define GREEK_SMALL_LETTER_FINAL_SIGMA 0x03C2
32 #define GREEK_SMALL_LETTER_SIGMA 0x03C3
34 nsTransformedTextRun
*
35 nsTransformedTextRun::Create(const gfxTextRunFactory::Parameters
* aParams
,
36 nsTransformingTextRunFactory
* aFactory
,
37 gfxFontGroup
* aFontGroup
,
38 const char16_t
* aString
, uint32_t aLength
,
39 const uint32_t aFlags
, nsStyleContext
** aStyles
,
42 NS_ASSERTION(!(aFlags
& gfxTextRunFactory::TEXT_IS_8BIT
),
43 "didn't expect text to be marked as 8-bit here");
45 void *storage
= AllocateStorageForTextRun(sizeof(nsTransformedTextRun
), aLength
);
50 return new (storage
) nsTransformedTextRun(aParams
, aFactory
, aFontGroup
,
52 aFlags
, aStyles
, aOwnsFactory
);
56 nsTransformedTextRun::SetCapitalization(uint32_t aStart
, uint32_t aLength
,
57 bool* aCapitalization
,
58 gfxContext
* aRefContext
)
60 if (mCapitalize
.IsEmpty()) {
61 if (!mCapitalize
.AppendElements(GetLength()))
63 memset(mCapitalize
.Elements(), 0, GetLength()*sizeof(bool));
65 memcpy(mCapitalize
.Elements() + aStart
, aCapitalization
, aLength
*sizeof(bool));
70 nsTransformedTextRun::SetPotentialLineBreaks(uint32_t aStart
, uint32_t aLength
,
71 uint8_t* aBreakBefore
,
72 gfxContext
* aRefContext
)
74 bool changed
= gfxTextRun::SetPotentialLineBreaks(aStart
, aLength
,
75 aBreakBefore
, aRefContext
);
83 nsTransformedTextRun::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
)
85 size_t total
= gfxTextRun::SizeOfExcludingThis(aMallocSizeOf
);
86 total
+= mStyles
.SizeOfExcludingThis(aMallocSizeOf
);
87 total
+= mCapitalize
.SizeOfExcludingThis(aMallocSizeOf
);
89 total
+= aMallocSizeOf(mFactory
);
95 nsTransformedTextRun::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
)
97 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
100 nsTransformedTextRun
*
101 nsTransformingTextRunFactory::MakeTextRun(const char16_t
* aString
, uint32_t aLength
,
102 const gfxTextRunFactory::Parameters
* aParams
,
103 gfxFontGroup
* aFontGroup
, uint32_t aFlags
,
104 nsStyleContext
** aStyles
, bool aOwnsFactory
)
106 return nsTransformedTextRun::Create(aParams
, this, aFontGroup
,
107 aString
, aLength
, aFlags
, aStyles
, aOwnsFactory
);
110 nsTransformedTextRun
*
111 nsTransformingTextRunFactory::MakeTextRun(const uint8_t* aString
, uint32_t aLength
,
112 const gfxTextRunFactory::Parameters
* aParams
,
113 gfxFontGroup
* aFontGroup
, uint32_t aFlags
,
114 nsStyleContext
** aStyles
, bool aOwnsFactory
)
116 // We'll only have a Unicode code path to minimize the amount of code needed
117 // for these rarely used features
118 NS_ConvertASCIItoUTF16
unicodeString(reinterpret_cast<const char*>(aString
), aLength
);
119 return MakeTextRun(unicodeString
.get(), aLength
, aParams
, aFontGroup
,
120 aFlags
& ~(gfxFontGroup::TEXT_IS_PERSISTENT
| gfxFontGroup::TEXT_IS_8BIT
),
121 aStyles
, aOwnsFactory
);
125 MergeCharactersInTextRun(gfxTextRun
* aDest
, gfxTextRun
* aSrc
,
126 const bool* aCharsToMerge
, const bool* aDeletedChars
)
128 aDest
->ResetGlyphRuns();
130 gfxTextRun::GlyphRunIterator
iter(aSrc
, 0, aSrc
->GetLength());
132 nsAutoTArray
<gfxTextRun::DetailedGlyph
,2> glyphs
;
133 while (iter
.NextRun()) {
134 gfxTextRun::GlyphRun
* run
= iter
.GetGlyphRun();
135 nsresult rv
= aDest
->AddGlyphRun(run
->mFont
, run
->mMatchType
,
136 offset
, false, run
->mOrientation
);
140 bool anyMissing
= false;
141 uint32_t mergeRunStart
= iter
.GetStringStart();
142 const gfxTextRun::CompressedGlyph
*srcGlyphs
= aSrc
->GetCharacterGlyphs();
143 gfxTextRun::CompressedGlyph mergedGlyph
= srcGlyphs
[mergeRunStart
];
144 uint32_t stringEnd
= iter
.GetStringEnd();
145 for (uint32_t k
= iter
.GetStringStart(); k
< stringEnd
; ++k
) {
146 const gfxTextRun::CompressedGlyph g
= srcGlyphs
[k
];
147 if (g
.IsSimpleGlyph()) {
149 gfxTextRun::DetailedGlyph details
;
150 details
.mGlyphID
= g
.GetSimpleGlyph();
151 details
.mAdvance
= g
.GetSimpleAdvance();
152 details
.mXOffset
= 0;
153 details
.mYOffset
= 0;
154 glyphs
.AppendElement(details
);
161 if (g
.GetGlyphCount() > 0) {
162 glyphs
.AppendElements(aSrc
->GetDetailedGlyphs(k
), g
.GetGlyphCount());
166 if (k
+ 1 < iter
.GetStringEnd() && aCharsToMerge
[k
+ 1]) {
167 // next char is supposed to merge with current, so loop without
168 // writing current merged glyph to the destination
172 // If the start of the merge run is actually a character that should
173 // have been merged with the previous character (this can happen
174 // if there's a font change in the middle of a case-mapped character,
175 // that decomposed into a sequence of base+diacritics, for example),
176 // just discard the entire merge run. See comment at start of this
178 NS_WARN_IF_FALSE(!aCharsToMerge
[mergeRunStart
],
179 "unable to merge across a glyph run boundary, "
180 "glyph(s) discarded");
181 if (!aCharsToMerge
[mergeRunStart
]) {
183 mergedGlyph
.SetMissing(glyphs
.Length());
185 mergedGlyph
.SetComplex(mergedGlyph
.IsClusterStart(),
186 mergedGlyph
.IsLigatureGroupStart(),
189 aDest
->SetGlyphs(offset
, mergedGlyph
, glyphs
.Elements());
192 while (offset
< aDest
->GetLength() && aDeletedChars
[offset
]) {
193 aDest
->SetGlyphs(offset
++, gfxTextRun::CompressedGlyph(), nullptr);
199 mergeRunStart
= k
+ 1;
200 if (mergeRunStart
< stringEnd
) {
201 mergedGlyph
= srcGlyphs
[mergeRunStart
];
204 NS_ASSERTION(glyphs
.Length() == 0,
205 "Leftover glyphs, don't request merging of the last character with its next!");
207 NS_ASSERTION(offset
== aDest
->GetLength(), "Bad offset calculations");
210 gfxTextRunFactory::Parameters
211 GetParametersForInner(nsTransformedTextRun
* aTextRun
, uint32_t* aFlags
,
212 gfxContext
* aRefContext
)
214 gfxTextRunFactory::Parameters params
=
215 { aRefContext
, nullptr, nullptr,
216 nullptr, 0, aTextRun
->GetAppUnitsPerDevUnit()
218 *aFlags
= aTextRun
->GetFlags() & ~gfxFontGroup::TEXT_IS_PERSISTENT
;
222 // Some languages have special casing conventions that differ from the
223 // default Unicode mappings.
224 // The enum values here are named for well-known exemplar languages that
225 // exhibit the behavior in question; multiple lang tags may map to the
226 // same setting here, if the behavior is shared by other languages.
227 enum LanguageSpecificCasingBehavior
{
228 eLSCB_None
, // default non-lang-specific behavior
229 eLSCB_Dutch
, // treat "ij" digraph as a unit for capitalization
230 eLSCB_Greek
, // strip accent when uppercasing Greek vowels
231 eLSCB_Irish
, // keep prefix letters as lowercase when uppercasing Irish
232 eLSCB_Turkish
// preserve dotted/dotless-i distinction in uppercase
235 static LanguageSpecificCasingBehavior
236 GetCasingFor(const nsIAtom
* aLang
)
241 if (aLang
== nsGkAtoms::tr
||
242 aLang
== nsGkAtoms::az
||
243 aLang
== nsGkAtoms::ba
||
244 aLang
== nsGkAtoms::crh
||
245 aLang
== nsGkAtoms::tt
) {
246 return eLSCB_Turkish
;
248 if (aLang
== nsGkAtoms::nl
) {
251 if (aLang
== nsGkAtoms::el
) {
254 if (aLang
== nsGkAtoms::ga
) {
258 // Is there a region subtag we should ignore?
259 nsAtomString
langStr(const_cast<nsIAtom
*>(aLang
));
260 int index
= langStr
.FindChar('-');
262 langStr
.Truncate(index
);
263 nsCOMPtr
<nsIAtom
> truncatedLang
= do_GetAtom(langStr
);
264 return GetCasingFor(truncatedLang
);
271 nsCaseTransformTextRunFactory::TransformString(
272 const nsAString
& aString
,
273 nsString
& aConvertedString
,
275 const nsIAtom
* aLanguage
,
276 nsTArray
<bool>& aCharsToMergeArray
,
277 nsTArray
<bool>& aDeletedCharsArray
,
278 nsTransformedTextRun
* aTextRun
,
279 nsTArray
<uint8_t>* aCanBreakBeforeArray
,
280 nsTArray
<nsStyleContext
*>* aStyleArray
)
282 NS_PRECONDITION(!aTextRun
|| (aCanBreakBeforeArray
&& aStyleArray
),
283 "either none or all three optional parameters required");
285 uint32_t length
= aString
.Length();
286 const char16_t
* str
= aString
.BeginReading();
288 bool mergeNeeded
= false;
290 bool capitalizeDutchIJ
= false;
291 bool prevIsLetter
= false;
292 bool ntPrefix
= false; // true immediately after a word-initial 'n' or 't'
293 // when doing Irish lowercasing
294 uint32_t sigmaIndex
= uint32_t(-1);
295 nsIUGenCategory::nsUGenCategory cat
;
297 uint8_t style
= aAllUppercase
? NS_STYLE_TEXT_TRANSFORM_UPPERCASE
: 0;
298 const nsIAtom
* lang
= aLanguage
;
300 LanguageSpecificCasingBehavior languageSpecificCasing
= GetCasingFor(lang
);
301 mozilla::GreekCasing::State greekState
;
302 mozilla::IrishCasing::State irishState
;
303 uint32_t irishMark
= uint32_t(-1); // location of possible prefix letter(s)
305 for (uint32_t i
= 0; i
< length
; ++i
) {
306 uint32_t ch
= str
[i
];
308 nsStyleContext
* styleContext
;
310 styleContext
= aTextRun
->mStyles
[i
];
311 style
= aAllUppercase
? NS_STYLE_TEXT_TRANSFORM_UPPERCASE
:
312 styleContext
->StyleText()->mTextTransform
;
314 const nsStyleFont
* styleFont
= styleContext
->StyleFont();
315 nsIAtom
* newLang
= styleFont
->mExplicitLanguage
316 ? styleFont
->mLanguage
: nullptr;
317 if (lang
!= newLang
) {
319 languageSpecificCasing
= GetCasingFor(lang
);
322 irishMark
= uint32_t(-1);
327 const mozilla::unicode::MultiCharMapping
*mcm
;
328 bool inhibitBreakBefore
= false; // have we just deleted preceding hyphen?
330 if (NS_IS_HIGH_SURROGATE(ch
) && i
< length
- 1 &&
331 NS_IS_LOW_SURROGATE(str
[i
+ 1])) {
332 ch
= SURROGATE_TO_UCS4(ch
, str
[i
+ 1]);
336 case NS_STYLE_TEXT_TRANSFORM_LOWERCASE
:
337 if (languageSpecificCasing
== eLSCB_Turkish
) {
339 ch
= LATIN_SMALL_LETTER_DOTLESS_I
;
341 sigmaIndex
= uint32_t(-1);
344 if (ch
== LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE
) {
347 sigmaIndex
= uint32_t(-1);
352 cat
= mozilla::unicode::GetGenCategory(ch
);
354 if (languageSpecificCasing
== eLSCB_Irish
&&
355 cat
== nsIUGenCategory::kLetter
) {
356 // See bug 1018805 for Irish lowercasing requirements
357 if (!prevIsLetter
&& (ch
== 'n' || ch
== 't')) {
360 if (ntPrefix
&& mozilla::IrishCasing::IsUpperVowel(ch
)) {
361 aConvertedString
.Append('-');
370 // Special lowercasing behavior for Greek Sigma: note that this is listed
371 // as context-sensitive in Unicode's SpecialCasing.txt, but is *not* a
372 // language-specific mapping; it applies regardless of the language of
375 // The lowercase mapping for CAPITAL SIGMA should be to SMALL SIGMA (i.e.
376 // the non-final form) whenever there is a following letter, or when the
377 // CAPITAL SIGMA occurs in isolation (neither preceded nor followed by a
378 // LETTER); and to FINAL SIGMA when it is preceded by another letter but
379 // not followed by one.
381 // To implement the context-sensitive nature of this mapping, we keep
382 // track of whether the previous character was a letter. If not, CAPITAL
383 // SIGMA will map directly to SMALL SIGMA. If the previous character
384 // was a letter, CAPITAL SIGMA maps to FINAL SIGMA and we record the
385 // position in the converted string; if we then encounter another letter,
386 // that FINAL SIGMA is replaced with a standard SMALL SIGMA.
388 // If sigmaIndex is not -1, it marks where we have provisionally mapped
389 // a CAPITAL SIGMA to FINAL SIGMA; if we now find another letter, we
390 // need to change it to SMALL SIGMA.
391 if (sigmaIndex
!= uint32_t(-1)) {
392 if (cat
== nsIUGenCategory::kLetter
) {
393 aConvertedString
.SetCharAt(GREEK_SMALL_LETTER_SIGMA
, sigmaIndex
);
397 if (ch
== GREEK_CAPITAL_LETTER_SIGMA
) {
398 // If preceding char was a letter, map to FINAL instead of SMALL,
399 // and note where it occurred by setting sigmaIndex; we'll change it
400 // to standard SMALL SIGMA later if another letter follows
402 ch
= GREEK_SMALL_LETTER_FINAL_SIGMA
;
403 sigmaIndex
= aConvertedString
.Length();
405 // CAPITAL SIGMA not preceded by a letter is unconditionally mapped
407 ch
= GREEK_SMALL_LETTER_SIGMA
;
408 sigmaIndex
= uint32_t(-1);
414 // ignore diacritics for the purpose of contextual sigma mapping;
415 // otherwise, reset prevIsLetter appropriately and clear the
417 if (cat
!= nsIUGenCategory::kMark
) {
418 prevIsLetter
= (cat
== nsIUGenCategory::kLetter
);
419 sigmaIndex
= uint32_t(-1);
422 mcm
= mozilla::unicode::SpecialLower(ch
);
425 while (j
< 2 && mcm
->mMappedChars
[j
+ 1]) {
426 aConvertedString
.Append(mcm
->mMappedChars
[j
]);
430 ch
= mcm
->mMappedChars
[j
];
434 ch
= ToLowerCase(ch
);
437 case NS_STYLE_TEXT_TRANSFORM_UPPERCASE
:
438 if (languageSpecificCasing
== eLSCB_Turkish
&& ch
== 'i') {
439 ch
= LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE
;
443 if (languageSpecificCasing
== eLSCB_Greek
) {
444 ch
= mozilla::GreekCasing::UpperCase(ch
, greekState
);
448 if (languageSpecificCasing
== eLSCB_Irish
) {
451 ch
= mozilla::IrishCasing::UpperCase(ch
, irishState
, mark
, action
);
453 irishMark
= aConvertedString
.Length();
456 nsString
& str
= aConvertedString
; // shorthand
459 // lowercase a single prefix letter
460 NS_ASSERTION(str
.Length() > 0 && irishMark
< str
.Length(),
462 str
.SetCharAt(ToLowerCase(str
[irishMark
]), irishMark
);
463 irishMark
= uint32_t(-1);
466 // lowercase two prefix letters (immediately before current pos)
467 NS_ASSERTION(str
.Length() >= 2 && irishMark
== str
.Length() - 2,
469 str
.SetCharAt(ToLowerCase(str
[irishMark
]), irishMark
);
470 str
.SetCharAt(ToLowerCase(str
[irishMark
+ 1]), irishMark
+ 1);
471 irishMark
= uint32_t(-1);
474 // lowercase one prefix letter, and delete following hyphen
475 // (which must be the immediately-preceding char)
476 NS_ASSERTION(str
.Length() >= 2 && irishMark
== str
.Length() - 2,
478 str
.Replace(irishMark
, 2, ToLowerCase(str
[irishMark
]));
479 aDeletedCharsArray
[irishMark
+ 1] = true;
480 // Remove the trailing entries (corresponding to the deleted hyphen)
481 // from the auxiliary arrays.
482 aCharsToMergeArray
.SetLength(aCharsToMergeArray
.Length() - 1);
484 aStyleArray
->SetLength(aStyleArray
->Length() - 1);
485 aCanBreakBeforeArray
->SetLength(aCanBreakBeforeArray
->Length() - 1);
486 inhibitBreakBefore
= true;
489 irishMark
= uint32_t(-1);
492 // ch has been set to the uppercase for current char;
493 // No need to check for SpecialUpper here as none of the characters
494 // that could trigger an Irish casing action have special mappings.
497 // If we didn't have any special action to perform, fall through
498 // to check for special uppercase (ß)
501 mcm
= mozilla::unicode::SpecialUpper(ch
);
504 while (j
< 2 && mcm
->mMappedChars
[j
+ 1]) {
505 aConvertedString
.Append(mcm
->mMappedChars
[j
]);
509 ch
= mcm
->mMappedChars
[j
];
513 ch
= ToUpperCase(ch
);
516 case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE
:
518 if (capitalizeDutchIJ
&& ch
== 'j') {
520 capitalizeDutchIJ
= false;
523 capitalizeDutchIJ
= false;
524 if (i
< aTextRun
->mCapitalize
.Length() && aTextRun
->mCapitalize
[i
]) {
525 if (languageSpecificCasing
== eLSCB_Turkish
&& ch
== 'i') {
526 ch
= LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE
;
529 if (languageSpecificCasing
== eLSCB_Dutch
&& ch
== 'i') {
531 capitalizeDutchIJ
= true;
535 mcm
= mozilla::unicode::SpecialTitle(ch
);
538 while (j
< 2 && mcm
->mMappedChars
[j
+ 1]) {
539 aConvertedString
.Append(mcm
->mMappedChars
[j
]);
543 ch
= mcm
->mMappedChars
[j
];
547 ch
= ToTitleCase(ch
);
552 case NS_STYLE_TEXT_TRANSFORM_FULLWIDTH
:
553 ch
= mozilla::unicode::GetFullWidth(ch
);
560 if (ch
== uint32_t(-1)) {
561 aDeletedCharsArray
.AppendElement(true);
564 aDeletedCharsArray
.AppendElement(false);
565 aCharsToMergeArray
.AppendElement(false);
567 aStyleArray
->AppendElement(styleContext
);
568 aCanBreakBeforeArray
->AppendElement(inhibitBreakBefore
? false :
569 aTextRun
->CanBreakLineBefore(i
));
573 aConvertedString
.Append(ch
);
575 aConvertedString
.Append(H_SURROGATE(ch
));
576 aConvertedString
.Append(L_SURROGATE(ch
));
578 aDeletedCharsArray
.AppendElement(true); // not exactly deleted, but the
579 // trailing surrogate is skipped
583 while (extraChars
-- > 0) {
585 aCharsToMergeArray
.AppendElement(true);
587 aStyleArray
->AppendElement(styleContext
);
588 aCanBreakBeforeArray
->AppendElement(false);
598 nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun
* aTextRun
,
599 gfxContext
* aRefContext
, gfxMissingFontRecorder
*aMFR
)
601 nsAutoString convertedString
;
602 nsAutoTArray
<bool,50> charsToMergeArray
;
603 nsAutoTArray
<bool,50> deletedCharsArray
;
604 nsAutoTArray
<uint8_t,50> canBreakBeforeArray
;
605 nsAutoTArray
<nsStyleContext
*,50> styleArray
;
607 bool mergeNeeded
= TransformString(aTextRun
->mString
,
614 &canBreakBeforeArray
,
618 gfxTextRunFactory::Parameters innerParams
=
619 GetParametersForInner(aTextRun
, &flags
, aRefContext
);
620 gfxFontGroup
* fontGroup
= aTextRun
->GetFontGroup();
622 nsAutoPtr
<nsTransformedTextRun
> transformedChild
;
623 nsAutoPtr
<gfxTextRun
> cachedChild
;
626 if (mInnerTransformingTextRunFactory
) {
627 transformedChild
= mInnerTransformingTextRunFactory
->MakeTextRun(
628 convertedString
.BeginReading(), convertedString
.Length(),
629 &innerParams
, fontGroup
, flags
, styleArray
.Elements(), false);
630 child
= transformedChild
.get();
632 cachedChild
= fontGroup
->MakeTextRun(
633 convertedString
.BeginReading(), convertedString
.Length(),
634 &innerParams
, flags
, aMFR
);
635 child
= cachedChild
.get();
639 // Copy potential linebreaks into child so they're preserved
640 // (and also child will be shaped appropriately)
641 NS_ASSERTION(convertedString
.Length() == canBreakBeforeArray
.Length(),
642 "Dropped characters or break-before values somewhere!");
643 child
->SetPotentialLineBreaks(0, canBreakBeforeArray
.Length(),
644 canBreakBeforeArray
.Elements(), aRefContext
);
645 if (transformedChild
) {
646 transformedChild
->FinishSettingProperties(aRefContext
, aMFR
);
650 // Now merge multiple characters into one multi-glyph character as required
651 // and deal with skipping deleted accent chars
652 NS_ASSERTION(charsToMergeArray
.Length() == child
->GetLength(),
653 "source length mismatch");
654 NS_ASSERTION(deletedCharsArray
.Length() == aTextRun
->GetLength(),
655 "destination length mismatch");
656 MergeCharactersInTextRun(aTextRun
, child
, charsToMergeArray
.Elements(),
657 deletedCharsArray
.Elements());
659 // No merging to do, so just copy; this produces a more optimized textrun.
660 // We can't steal the data because the child may be cached and stealing
661 // the data would break the cache.
662 aTextRun
->ResetGlyphRuns();
663 aTextRun
->CopyGlyphDataFrom(child
, 0, child
->GetLength(), 0);