OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright (c) 2012 Google Inc. All rights reserved. | 2 * Copyright (c) 2012 Google Inc. All rights reserved. |
3 * Copyright (C) 2013 BlackBerry Limited. All rights reserved. | 3 * Copyright (C) 2013 BlackBerry Limited. All rights reserved. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions are | 6 * modification, are permitted provided that the following conditions are |
7 * met: | 7 * met: |
8 * | 8 * |
9 * * Redistributions of source code must retain the above copyright | 9 * * Redistributions of source code must retain the above copyright |
10 * notice, this list of conditions and the following disclaimer. | 10 * notice, this list of conditions and the following disclaimer. |
(...skipping 16 matching lines...) Expand all Loading... | |
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 */ | 30 */ |
31 | 31 |
32 #include "config.h" | 32 #include "config.h" |
33 #include "platform/fonts/shaping/HarfBuzzShaper.h" | 33 #include "platform/fonts/shaping/HarfBuzzShaper.h" |
34 | 34 |
35 #include "hb.h" | 35 #include "hb.h" |
36 #include "platform/LayoutUnit.h" | 36 #include "platform/LayoutUnit.h" |
37 #include "platform/Logging.h" | |
37 #include "platform/RuntimeEnabledFeatures.h" | 38 #include "platform/RuntimeEnabledFeatures.h" |
38 #include "platform/fonts/Character.h" | 39 #include "platform/fonts/Character.h" |
39 #include "platform/fonts/Font.h" | 40 #include "platform/fonts/Font.h" |
41 #include "platform/fonts/FontFallbackIterator.h" | |
40 #include "platform/fonts/GlyphBuffer.h" | 42 #include "platform/fonts/GlyphBuffer.h" |
41 #include "platform/fonts/UTF16TextIterator.h" | 43 #include "platform/fonts/UTF16TextIterator.h" |
42 #include "platform/fonts/shaping/HarfBuzzFace.h" | 44 #include "platform/fonts/shaping/HarfBuzzFace.h" |
45 #include "platform/fonts/shaping/RunSegmenter.h" | |
43 #include "platform/text/TextBreakIterator.h" | 46 #include "platform/text/TextBreakIterator.h" |
44 #include "wtf/Compiler.h" | 47 #include "wtf/Compiler.h" |
45 #include "wtf/MathExtras.h" | 48 #include "wtf/MathExtras.h" |
46 #include "wtf/text/Unicode.h" | 49 #include "wtf/text/Unicode.h" |
47 | 50 |
48 #include <algorithm> | 51 #include <algorithm> |
49 #include <list> | 52 #include <list> |
50 #include <map> | 53 #include <map> |
51 #include <string> | 54 #include <string> |
52 #include <unicode/normlzr.h> | 55 #include <unicode/normlzr.h> |
(...skipping 719 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
772 hb_feature_t feature; | 775 hb_feature_t feature; |
773 const AtomicString& tag = settings->at(i).tag(); | 776 const AtomicString& tag = settings->at(i).tag(); |
774 feature.tag = HB_TAG(tag[0], tag[1], tag[2], tag[3]); | 777 feature.tag = HB_TAG(tag[0], tag[1], tag[2], tag[3]); |
775 feature.value = settings->at(i).value(); | 778 feature.value = settings->at(i).value(); |
776 feature.start = 0; | 779 feature.start = 0; |
777 feature.end = static_cast<unsigned>(-1); | 780 feature.end = static_cast<unsigned>(-1); |
778 m_features.append(feature); | 781 m_features.append(feature); |
779 } | 782 } |
780 } | 783 } |
781 | 784 |
782 PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult() | |
783 { | |
784 if (!createHarfBuzzRuns()) | |
785 return nullptr; | |
786 return shapeHarfBuzzRuns(); | |
787 } | |
788 | |
789 struct CandidateRun { | |
790 UChar32 character; | |
791 unsigned start; | |
792 unsigned end; | |
793 const SimpleFontData* fontData; | |
794 UScriptCode script; | |
795 }; | |
796 | |
797 static inline bool collectCandidateRuns(const UChar* normalizedBuffer, | |
798 size_t bufferLength, const Font* font, Vector<CandidateRun>* runs, bool isSp aceNormalize) | |
799 { | |
800 UTF16TextIterator iterator(normalizedBuffer, bufferLength); | |
801 UChar32 character; | |
802 unsigned startIndexOfCurrentRun = 0; | |
803 | |
804 if (!iterator.consume(character)) | |
805 return false; | |
806 | |
807 const SimpleFontData* nextFontData = font->glyphDataForCharacter(character, false, isSpaceNormalize).fontData; | |
808 UErrorCode errorCode = U_ZERO_ERROR; | |
809 UScriptCode nextScript = uscript_getScript(character, &errorCode); | |
810 if (U_FAILURE(errorCode)) | |
811 return false; | |
812 | |
813 do { | |
814 const UChar* currentCharacterPosition = iterator.characters(); | |
815 const SimpleFontData* currentFontData = nextFontData; | |
816 UScriptCode currentScript = nextScript; | |
817 | |
818 UChar32 lastCharacter = character; | |
819 for (iterator.advance(); iterator.consume(character); iterator.advance() ) { | |
820 if (Character::treatAsZeroWidthSpace(character)) | |
821 continue; | |
822 if ((U_GET_GC_MASK(character) & U_GC_M_MASK) | |
823 && (Character::isUnicodeVariationSelector(character) | |
824 || currentFontData->canRenderCombiningCharacterSequence( | |
825 currentCharacterPosition, | |
826 iterator.glyphEnd() - currentCharacterPosition))) | |
827 continue; | |
828 | |
829 nextFontData = font->glyphDataForCharacter(character, false, isSpace Normalize).fontData; | |
830 nextScript = uscript_getScript(character, &errorCode); | |
831 if (U_FAILURE(errorCode)) | |
832 return false; | |
833 if (lastCharacter == zeroWidthJoinerCharacter) | |
834 currentFontData = nextFontData; | |
835 if ((nextFontData != currentFontData) || ((currentScript != nextScri pt) && (nextScript != USCRIPT_INHERITED) && (!uscript_hasScript(character, curre ntScript)))) | |
836 break; | |
837 currentCharacterPosition = iterator.characters(); | |
838 lastCharacter = character; | |
839 } | |
840 | |
841 CandidateRun run = { lastCharacter, startIndexOfCurrentRun, static_cast< unsigned>(iterator.offset()), currentFontData, currentScript }; | |
842 runs->append(run); | |
843 | |
844 startIndexOfCurrentRun = iterator.offset(); | |
845 } while (iterator.consume(character)); | |
846 | |
847 return true; | |
848 } | |
849 | |
850 static inline bool matchesAdjacentRun(UScriptCode* scriptExtensions, int length, | |
851 CandidateRun& adjacentRun) | |
852 { | |
853 for (int i = 0; i < length; i++) { | |
854 if (scriptExtensions[i] == adjacentRun.script) | |
855 return true; | |
856 } | |
857 return false; | |
858 } | |
859 | |
860 static inline void resolveRunBasedOnScriptExtensions(Vector<CandidateRun>& runs, | |
861 CandidateRun& run, size_t i, size_t length, UScriptCode* scriptExtensions, | |
862 int extensionsLength, size_t& nextResolvedRun) | |
863 { | |
864 // If uscript_getScriptExtensions returns 1 it only contains the script valu e, | |
865 // we only care about ScriptExtensions which is indicated by a value >= 2. | |
866 if (extensionsLength <= 1) | |
867 return; | |
868 | |
869 if (i > 0 && matchesAdjacentRun(scriptExtensions, extensionsLength, runs[i - 1])) { | |
870 run.script = runs[i - 1].script; | |
871 return; | |
872 } | |
873 | |
874 for (size_t j = i + 1; j < length; j++) { | |
875 if (runs[j].script != USCRIPT_COMMON | |
876 && runs[j].script != USCRIPT_INHERITED | |
877 && matchesAdjacentRun(scriptExtensions, extensionsLength, runs[j])) { | |
878 nextResolvedRun = j; | |
879 break; | |
880 } | |
881 } | |
882 } | |
883 | |
884 static inline void resolveRunBasedOnScriptValue(Vector<CandidateRun>& runs, | |
885 CandidateRun& run, size_t i, size_t length, size_t& nextResolvedRun) | |
886 { | |
887 if (run.script != USCRIPT_COMMON) | |
888 return; | |
889 | |
890 if (i > 0 && runs[i - 1].script != USCRIPT_COMMON) { | |
891 run.script = runs[i - 1].script; | |
892 return; | |
893 } | |
894 | |
895 for (size_t j = i + 1; j < length; j++) { | |
896 if (runs[j].script != USCRIPT_COMMON | |
897 && runs[j].script != USCRIPT_INHERITED) { | |
898 nextResolvedRun = j; | |
899 break; | |
900 } | |
901 } | |
902 } | |
903 | |
904 static inline bool resolveCandidateRuns(Vector<CandidateRun>& runs) | |
905 { | |
906 UScriptCode scriptExtensions[USCRIPT_CODE_LIMIT]; | |
907 UErrorCode errorCode = U_ZERO_ERROR; | |
908 size_t length = runs.size(); | |
909 for (size_t i = 0; i < length; i++) { | |
910 CandidateRun& run = runs[i]; | |
911 size_t nextResolvedRun = 0; | |
912 | |
913 if (run.script == USCRIPT_INHERITED) | |
914 run.script = i > 0 ? runs[i - 1].script : USCRIPT_COMMON; | |
915 | |
916 int extensionsLength = uscript_getScriptExtensions(run.character, | |
917 scriptExtensions, sizeof(scriptExtensions) / sizeof(scriptExtensions [0]), | |
918 &errorCode); | |
919 if (U_FAILURE(errorCode)) | |
920 return false; | |
921 | |
922 resolveRunBasedOnScriptExtensions(runs, run, i, length, | |
923 scriptExtensions, extensionsLength, nextResolvedRun); | |
924 resolveRunBasedOnScriptValue(runs, run, i, length, | |
925 nextResolvedRun); | |
926 for (size_t j = i; j < nextResolvedRun; j++) | |
927 runs[j].script = runs[nextResolvedRun].script; | |
928 | |
929 i = std::max(i, nextResolvedRun); | |
930 } | |
931 return true; | |
932 } | |
933 | |
934 // For ideographic (CJK) documents, 90-95% of calls from width() are one charact er length | |
935 // because most characters have break opportunities both before and after. | |
936 bool HarfBuzzShaper::createHarfBuzzRunsForSingleCharacter() | |
937 { | |
938 ASSERT(m_normalizedBufferLength == 1); | |
939 UChar32 character = m_normalizedBuffer[0]; | |
940 if (!U16_IS_SINGLE(character)) | |
941 return false; | |
942 const SimpleFontData* fontData = m_font->glyphDataForCharacter(character, fa lse, m_textRun.normalizeSpace()).fontData; | |
943 UErrorCode errorCode = U_ZERO_ERROR; | |
944 UScriptCode script = uscript_getScript(character, &errorCode); | |
945 if (U_FAILURE(errorCode)) | |
946 return false; | |
947 addHarfBuzzRun(0, 1, fontData, script); | |
948 return true; | |
949 } | |
950 | |
951 bool HarfBuzzShaper::createHarfBuzzRuns() | |
952 { | |
953 if (m_normalizedBufferLength == 1) | |
954 return createHarfBuzzRunsForSingleCharacter(); | |
955 | |
956 Vector<CandidateRun> candidateRuns; | |
957 if (!collectCandidateRuns(m_normalizedBuffer.get(), | |
958 m_normalizedBufferLength, m_font, &candidateRuns, m_textRun.normalizeSpa ce())) | |
959 return false; | |
960 | |
961 if (!resolveCandidateRuns(candidateRuns)) | |
962 return false; | |
963 | |
964 size_t length = candidateRuns.size(); | |
965 for (size_t i = 0; i < length; ) { | |
966 CandidateRun& run = candidateRuns[i]; | |
967 CandidateRun lastMatchingRun = run; | |
968 for (i++; i < length; i++) { | |
969 if (candidateRuns[i].script != run.script | |
970 || candidateRuns[i].fontData != run.fontData) | |
971 break; | |
972 lastMatchingRun = candidateRuns[i]; | |
973 } | |
974 addHarfBuzzRun(run.start, lastMatchingRun.end, run.fontData, run.script) ; | |
975 } | |
976 return !m_harfBuzzRuns.isEmpty(); | |
977 } | |
978 | |
979 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built | 785 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built |
980 // without hb-icu. See http://crbug.com/356929 | 786 // without hb-icu. See http://crbug.com/356929 |
981 static inline hb_script_t ICUScriptToHBScript(UScriptCode script) | 787 static inline hb_script_t ICUScriptToHBScript(UScriptCode script) |
982 { | 788 { |
983 if (UNLIKELY(script == USCRIPT_INVALID_CODE)) | 789 if (UNLIKELY(script == USCRIPT_INVALID_CODE)) |
984 return HB_SCRIPT_INVALID; | 790 return HB_SCRIPT_INVALID; |
985 | 791 |
986 return hb_script_from_string(uscript_getShortName(script), -1); | 792 return hb_script_from_string(uscript_getShortName(script), -1); |
987 } | 793 } |
988 | 794 |
989 static inline hb_direction_t TextDirectionToHBDirection(TextDirection dir, FontO rientation orientation, const SimpleFontData* fontData) | 795 static inline hb_direction_t TextDirectionToHBDirection(TextDirection dir, FontO rientation orientation, const SimpleFontData* fontData) |
990 { | 796 { |
991 hb_direction_t harfBuzzDirection = isVerticalAnyUpright(orientation) && !fon tData->isTextOrientationFallback() ? HB_DIRECTION_TTB : HB_DIRECTION_LTR; | 797 hb_direction_t harfBuzzDirection = isVerticalAnyUpright(orientation) && !fon tData->isTextOrientationFallback() ? HB_DIRECTION_TTB : HB_DIRECTION_LTR; |
992 return dir == RTL ? HB_DIRECTION_REVERSE(harfBuzzDirection) : harfBuzzDirect ion; | 798 return dir == RTL ? HB_DIRECTION_REVERSE(harfBuzzDirection) : harfBuzzDirect ion; |
993 } | 799 } |
994 | 800 |
995 void HarfBuzzShaper::addHarfBuzzRun(unsigned startCharacter, | |
996 unsigned endCharacter, const SimpleFontData* fontData, | |
997 UScriptCode script) | |
998 { | |
999 ASSERT(endCharacter > startCharacter); | |
1000 ASSERT(script != USCRIPT_INVALID_CODE); | |
1001 | |
1002 hb_direction_t direction = TextDirectionToHBDirection(m_textRun.direction(), | |
1003 m_font->fontDescription().orientation(), fontData); | |
1004 HarfBuzzRun harfBuzzRun = { | |
1005 fontData, startCharacter, endCharacter - startCharacter, | |
1006 direction, ICUScriptToHBScript(script) | |
1007 }; | |
1008 m_harfBuzzRuns.append(harfBuzzRun); | |
1009 } | |
1010 | |
1011 static const uint16_t* toUint16(const UChar* src) | 801 static const uint16_t* toUint16(const UChar* src) |
1012 { | 802 { |
1013 // FIXME: This relies on undefined behavior however it works on the | 803 // FIXME: This relies on undefined behavior however it works on the |
1014 // current versions of all compilers we care about and avoids making | 804 // current versions of all compilers we care about and avoids making |
1015 // a copy of the string. | 805 // a copy of the string. |
1016 static_assert(sizeof(UChar) == sizeof(uint16_t), "UChar should be the same s ize as uint16_t"); | 806 static_assert(sizeof(UChar) == sizeof(uint16_t), "UChar should be the same s ize as uint16_t"); |
1017 return reinterpret_cast<const uint16_t*>(src); | 807 return reinterpret_cast<const uint16_t*>(src); |
1018 } | 808 } |
1019 | 809 |
1020 static inline void addToHarfBuzzBufferInternal(hb_buffer_t* buffer, | 810 static inline void addToHarfBuzzBufferInternal(hb_buffer_t* buffer, |
1021 const FontDescription& fontDescription, const UChar* normalizedBuffer, | 811 const FontDescription& fontDescription, const UChar* normalizedBuffer, |
1022 unsigned startIndex, unsigned numCharacters) | 812 unsigned startIndex, unsigned numCharacters) |
1023 { | 813 { |
1024 if (fontDescription.variant() == FontVariantSmallCaps | 814 // TODO: Revisit whether we can always fill the hb_buffer_t with the |
1025 && u_islower(normalizedBuffer[startIndex])) { | 815 // full run text, but only specify startIndex and numCharacters for the part |
816 // to be shaped. Then simplify/change the complicated index computations in | |
817 // extractShapeResults(). | |
818 if (fontDescription.variant() == FontVariantSmallCaps) { | |
1026 String upperText = String(normalizedBuffer + startIndex, numCharacters) | 819 String upperText = String(normalizedBuffer + startIndex, numCharacters) |
1027 .upper(); | 820 .upper(); |
1028 // TextRun is 16 bit, therefore upperText is 16 bit, even after we call | 821 // TextRun is 16 bit, therefore upperText is 16 bit, even after we call |
1029 // makeUpper(). | 822 // makeUpper(). |
1030 ASSERT(!upperText.is8Bit()); | 823 ASSERT(!upperText.is8Bit()); |
1031 hb_buffer_add_utf16(buffer, toUint16(upperText.characters16()), | 824 hb_buffer_add_utf16(buffer, toUint16(upperText.characters16()), |
1032 numCharacters, 0, numCharacters); | 825 numCharacters, 0, numCharacters); |
1033 } else { | 826 } else { |
1034 hb_buffer_add_utf16(buffer, toUint16(normalizedBuffer + startIndex), | 827 hb_buffer_add_utf16(buffer, toUint16(normalizedBuffer + startIndex), |
1035 numCharacters, 0, numCharacters); | 828 numCharacters, 0, numCharacters); |
1036 } | 829 } |
1037 } | 830 } |
1038 | 831 |
1039 PassRefPtr<ShapeResult> HarfBuzzShaper::shapeHarfBuzzRuns() | 832 inline bool HarfBuzzShaper::shapeRange(hb_buffer_t* harfBuzzBuffer, |
833 int startIndex, | |
834 int numCharacters, | |
835 const SimpleFontData* currentFont, | |
836 UScriptCode currentRunScript, | |
837 hb_language_t language) | |
838 { | |
839 FontPlatformData* platformData = const_cast<FontPlatformData*>(&(currentFont ->platformData())); | |
eae
2015/10/14 08:12:00
Is this const_cast really needed? harfBuzzFace() i
| |
840 HarfBuzzFace* face = platformData->harfBuzzFace(); | |
841 if (!face) { | |
842 WTF_LOG_ERROR("Could not create HarfBuzzFace from FontPlatformData."); | |
843 return false; | |
844 } | |
845 | |
846 hb_buffer_set_language(harfBuzzBuffer, language); | |
847 hb_buffer_set_script(harfBuzzBuffer, ICUScriptToHBScript(currentRunScript)); | |
848 hb_buffer_set_direction(harfBuzzBuffer, TextDirectionToHBDirection(m_textRun .direction(), | |
849 m_font->fontDescription().orientation(), currentFont)); | |
850 | |
851 // Add a space as pre-context to the buffer. This prevents showing dotted-ci rcle | |
852 // for combining marks at the beginning of runs. | |
853 static const uint16_t preContext = spaceCharacter; | |
854 hb_buffer_add_utf16(harfBuzzBuffer, &preContext, 1, 1, 0); | |
eae
2015/10/14 08:12:01
We don't need this any more, right? (If you want t
| |
855 | |
856 addToHarfBuzzBufferInternal(harfBuzzBuffer, | |
857 m_font->fontDescription(), m_normalizedBuffer.get(), startIndex, | |
858 numCharacters); | |
859 | |
860 HarfBuzzScopedPtr<hb_font_t> harfBuzzFont(face->createFont(), hb_font_destro y); | |
861 hb_shape(harfBuzzFont.get(), harfBuzzBuffer, m_features.isEmpty() ? 0 : m_fe atures.data(), m_features.size()); | |
862 | |
863 return true; | |
864 } | |
865 | |
866 bool HarfBuzzShaper::extractShapeResults(hb_buffer_t* harfBuzzBuffer, | |
867 ShapeResult* shapeResult, | |
eae
2015/10/14 08:12:01
const ShapeResult?
| |
868 Deque<HarfBuzzShaper::HolesQueueItem>* holesQueue, | |
869 bool& fontCycleQueued, const HolesQueueItem& currentQueueItem, | |
870 const SimpleFontData* currentFont, | |
871 UScriptCode currentRunScript, | |
872 bool isLastResort) | |
873 { | |
874 enum ClusterResult { | |
875 Shaped, | |
876 NotDef, | |
877 Unknown | |
878 }; | |
879 ClusterResult currentClusterResult = Unknown; | |
880 ClusterResult previousClusterResult = Unknown; | |
881 unsigned previousCluster = 0; | |
882 unsigned currentCluster = 0; | |
883 | |
884 // Find first notdef glyph in harfBuzzBuffer. | |
885 int numGlyphs = hb_buffer_get_length(harfBuzzBuffer); | |
eae
2015/10/14 08:12:01
unsigned?
| |
886 hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos(harfBuzzBuffer, 0); | |
887 | |
888 int lastChangePosition = 0; | |
eae
2015/10/14 08:12:00
unsigned
| |
889 | |
890 if (!numGlyphs) { | |
891 WTF_LOG_ERROR("HarfBuzz returned empty glyph buffer after shaping."); | |
892 return false; | |
893 } | |
894 | |
895 for (int j = 0; j <= numGlyphs; ++j) { | |
eae
2015/10/14 08:12:00
unsigned glyphIndex or i?
| |
896 // Iterating by clusters, check for when the state switches from shaped | |
897 // to non-shaped and vice versa. Taking into account the edge cases of | |
898 // beginning of the run and end of the run. | |
899 previousCluster = currentCluster; | |
900 currentCluster = glyphInfo[j].cluster; | |
901 | |
902 if (j < numGlyphs) { | |
903 // Still the same cluster, merge shaping status. | |
904 if (previousCluster == currentCluster && j != 0) { | |
905 if (glyphInfo[j].codepoint == 0) { | |
906 currentClusterResult = NotDef; | |
907 } else { | |
908 currentClusterResult = currentClusterResult == Shaped ? Shap ed : NotDef; | |
eae
2015/10/14 08:12:01
When would currentClusterResult be anything but Sh
| |
909 } | |
910 continue; | |
911 } | |
912 // We've moved to a new cluster. | |
913 previousClusterResult = currentClusterResult; | |
914 currentClusterResult = glyphInfo[j].codepoint == 0 ? NotDef : Shaped ; | |
915 } else { | |
916 previousClusterResult = currentClusterResult; | |
917 // Terminate explicitly at the end. | |
918 if (previousClusterResult == NotDef) { | |
919 currentClusterResult = Shaped; | |
920 } else { | |
921 currentClusterResult = NotDef; | |
922 } | |
eae
2015/10/14 08:12:01
This logic is a little hard to follow. If the curr
| |
923 } | |
924 | |
925 bool atChange = (previousClusterResult != currentClusterResult) && previ ousClusterResult != Unknown; | |
926 if (!atChange) | |
927 continue; | |
928 | |
929 // Compute the range indices of consecutive shaped or .notdef glyphs. | |
930 // Cluster information for RTL runs becomes reversed, e.g. character 0 | |
931 // has cluster index 5 in a run of 6 characters. | |
932 int numCharacters = 0; | |
eae
2015/10/14 08:12:01
Can we use unsigned for these? Mixing signed and u
| |
933 int numGlyphsToInsert = 0; | |
934 int startIndex = 0; | |
935 if (HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harfBuzzBuffer))) { | |
936 startIndex = currentQueueItem.m_startIndex + glyphInfo[lastChangePos ition].cluster; | |
937 if (j == numGlyphs) { | |
938 numCharacters = currentQueueItem.m_numCharacters - glyphInfo[las tChangePosition].cluster; | |
939 numGlyphsToInsert = numGlyphs - lastChangePosition; | |
940 } else { | |
941 numCharacters = glyphInfo[j].cluster - glyphInfo[lastChangePosit ion].cluster; | |
942 numGlyphsToInsert = j - lastChangePosition; | |
943 } | |
944 } else { | |
eae
2015/10/14 08:12:01
// Direction Backwards
| |
945 startIndex = currentQueueItem.m_startIndex + glyphInfo[j - 1].cluste r; | |
946 if (lastChangePosition == 0) { | |
947 numCharacters = currentQueueItem.m_numCharacters - glyphInfo[j - 1].cluster; | |
948 } else { | |
949 numCharacters = glyphInfo[lastChangePosition - 1].cluster - glyp hInfo[j - 1].cluster; | |
950 } | |
951 numGlyphsToInsert = j - lastChangePosition; | |
952 } | |
953 | |
954 if (currentClusterResult == Shaped && !isLastResort) { | |
955 // Now it's clear that we need to continue processing. | |
956 if (!fontCycleQueued) { | |
957 holesQueue->append(HolesQueueItem(HolesQueueNextFont, 0, 0)); | |
958 fontCycleQueued = true; | |
959 } | |
960 | |
961 // Here we need to put character positions. | |
962 ASSERT(numCharacters); | |
963 holesQueue->append(HolesQueueItem(HolesQueueRange, | |
964 startIndex, numCharacters)); | |
965 } | |
966 | |
967 // If numCharacters is 0, that means we hit a NotDef before shaping the | |
968 // whole grapheme. We do not append it here. For the next glyph we | |
969 // encounter, atChange will be true, and the characters corresponding to | |
970 // the grapheme will be added to the TODO queue again, attempting to | |
971 // shape the whole grapheme with the next font. | |
972 // When we're getting here with the last resort font, we have no other | |
973 // choice than adding boxes to the ShapeResult. | |
974 if ((currentClusterResult == NotDef && numCharacters != 0) || isLastReso rt) { | |
eae
2015/10/14 08:12:01
&& numCharacters will do. No need to have the expl
| |
975 // Here we need to specify glyph positions. | |
976 OwnPtr<ShapeResult::RunInfo> run = adoptPtr(new ShapeResult::RunInfo (currentFont, | |
977 TextDirectionToHBDirection(m_textRun.direction(), | |
978 m_font->fontDescription().orientation(), currentFont), | |
979 ICUScriptToHBScript(currentRunScript), | |
980 startIndex, | |
981 numGlyphsToInsert, numCharacters)); | |
982 insertRunIntoShapeResult(shapeResult, run.release(), lastChangePosit ion, numGlyphsToInsert, currentQueueItem.m_startIndex, harfBuzzBuffer); | |
983 } | |
984 lastChangePosition = j; | |
985 } | |
986 return true; | |
987 } | |
988 | |
989 static inline const SimpleFontData* mangleFontDirection(const SimpleFontData* or iginalFont, | |
eae
2015/10/14 08:12:01
The name makes it sound like this operates on the
| |
990 FontOrientation runOrientation, | |
991 OrientationIterator::RenderOrientation renderOrientation) | |
992 { | |
993 if (!isVerticalBaseline(runOrientation)) | |
994 return originalFont; | |
995 | |
996 if (runOrientation == FontOrientation::VerticalRotated | |
997 || (runOrientation == FontOrientation::VerticalMixed && renderOrientatio n == OrientationIterator::OrientationRotateSideways)) | |
998 return originalFont->verticalRightOrientationFontData().get(); | |
999 | |
1000 return originalFont; | |
1001 } | |
1002 | |
1003 bool HarfBuzzShaper::buildHint(Vector<UChar32>& hint, bool needsList) | |
1004 { | |
1005 if (!m_holesQueue.size()) | |
1006 return false; | |
1007 | |
1008 hint.clear(); | |
1009 | |
1010 size_t numCharsAdded = 0; | |
1011 for (auto it = m_holesQueue.begin(); it != m_holesQueue.end(); ++it) { | |
1012 if (it->m_action == HolesQueueNextFont) | |
1013 break; | |
1014 | |
1015 UChar32 hintChar; | |
1016 UTF16TextIterator iterator(m_normalizedBuffer.get() + it->m_startIndex, it->m_numCharacters); | |
eae
2015/10/14 08:12:01
Could we add an assert ensuring that the index is
| |
1017 while (iterator.consume(hintChar)) { | |
1018 hint.append(hintChar); | |
1019 numCharsAdded++; | |
1020 if (!needsList) | |
1021 break; | |
1022 iterator.advance(); | |
1023 } | |
1024 } | |
1025 return numCharsAdded > 0; | |
1026 } | |
1027 | |
1028 PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult() | |
1040 { | 1029 { |
1041 RefPtr<ShapeResult> result = ShapeResult::create(m_font, | 1030 RefPtr<ShapeResult> result = ShapeResult::create(m_font, |
1042 m_normalizedBufferLength, m_textRun.direction()); | 1031 m_normalizedBufferLength, m_textRun.direction()); |
1043 HarfBuzzScopedPtr<hb_buffer_t> harfBuzzBuffer(hb_buffer_create(), hb_buffer_ destroy); | 1032 HarfBuzzScopedPtr<hb_buffer_t> harfBuzzBuffer(hb_buffer_create(), hb_buffer_ destroy); |
1044 | 1033 |
1045 const FontDescription& fontDescription = m_font->fontDescription(); | 1034 const FontDescription& fontDescription = m_font->fontDescription(); |
1046 const String& localeString = fontDescription.locale(); | 1035 const String& localeString = fontDescription.locale(); |
1047 CString locale = localeString.latin1(); | 1036 CString locale = localeString.latin1(); |
1048 const hb_language_t language = hb_language_from_string(locale.data(), locale .length()); | 1037 const hb_language_t language = hb_language_from_string(locale.data(), locale .length()); |
1049 | 1038 |
1050 result->m_runs.resize(m_harfBuzzRuns.size()); | 1039 RunSegmenter::RunSegmenterRange segmentRange = {0, 0, USCRIPT_INVALID_CODE, OrientationIterator::OrientationInvalid, SmallCapsIterator::SmallCapsSameCase }; |
eae
2015/10/14 08:12:01
These two lines are really long at > 150, would yo
| |
1051 for (unsigned i = 0; i < m_harfBuzzRuns.size(); ++i) { | 1040 RunSegmenter runSegmenter(m_normalizedBuffer.get(), m_normalizedBufferLength , m_font->fontDescription().orientation(), fontDescription.variant()); |
1052 unsigned runIndex = m_textRun.rtl() ? m_harfBuzzRuns.size() - i - 1 : i; | 1041 |
1053 const HarfBuzzRun* currentRun = &m_harfBuzzRuns[runIndex]; | 1042 Vector<UChar32> hint; |
1054 | 1043 |
1055 const SimpleFontData* currentFontData = currentRun->m_fontData; | 1044 // TODO: Check whether this treatAsZerowidthspace from the previous script |
1056 FontPlatformData* platformData = const_cast<FontPlatformData*>(¤tF ontData->platformData()); | 1045 // segmentation plays a role here, does the new scriptRuniterator handle tha t correctly? |
1057 HarfBuzzFace* face = platformData->harfBuzzFace(); | 1046 while (runSegmenter.consume(&segmentRange)) { |
1058 if (!face) | 1047 RefPtr<FontFallbackIterator> fallbackIterator = m_font->createFontFallba ckIterator(); |
1059 return nullptr; | 1048 |
1060 | 1049 m_holesQueue.append(HolesQueueItem(HolesQueueNextFont, 0, 0)); |
eae
2015/10/14 08:12:01
Having a static helper for appending might make th
| |
1061 hb_buffer_set_language(harfBuzzBuffer.get(), language); | 1050 m_holesQueue.append(HolesQueueItem(HolesQueueRange, segmentRange.start, segmentRange.end - segmentRange.start)); |
1062 hb_buffer_set_script(harfBuzzBuffer.get(), currentRun->m_script); | 1051 |
1063 hb_buffer_set_direction(harfBuzzBuffer.get(), currentRun->m_direction); | 1052 const SimpleFontData* currentFont = nullptr; |
1064 | 1053 |
1065 // Add a space as pre-context to the buffer. This prevents showing dotte d-circle | 1054 bool fontCycleQueued = false; |
1066 // for combining marks at the beginning of runs. | 1055 while (m_holesQueue.size()) { |
1067 static const uint16_t preContext = spaceCharacter; | 1056 HolesQueueItem currentQueueItem = m_holesQueue.takeFirst(); |
1068 hb_buffer_add_utf16(harfBuzzBuffer.get(), &preContext, 1, 1, 0); | 1057 |
1069 | 1058 if (currentQueueItem.m_action == HolesQueueNextFont) { |
1070 addToHarfBuzzBufferInternal(harfBuzzBuffer.get(), | 1059 // For now, we're building a character list with which we probe |
1071 fontDescription, m_normalizedBuffer.get(), currentRun->m_startIndex, | 1060 // for needed fonts depending on the declared unicode-range of a |
1072 currentRun->m_numCharacters); | 1061 // segmented CSS font. Alternatively, we can build a fake font |
1073 | 1062 // for the shaper and check whether any glyphs were found, or |
1074 HarfBuzzScopedPtr<hb_font_t> harfBuzzFont(face->createFont(), hb_font_de stroy); | 1063 // define a new API on the shaper which will give us coverage |
1075 hb_shape(harfBuzzFont.get(), harfBuzzBuffer.get(), m_features.isEmpty() ? 0 : m_features.data(), m_features.size()); | 1064 // information? |
1076 shapeResult(result.get(), i, currentRun, harfBuzzBuffer.get()); | 1065 if (!buildHint(hint, fallbackIterator->needsHintList())) { |
1077 | 1066 // Give up shaping since we cannot retrieve a font fallback |
1078 hb_buffer_reset(harfBuzzBuffer.get()); | 1067 // font without a hintlist. |
1079 } | 1068 m_holesQueue.clear(); |
1080 | 1069 break; |
1081 // We should have consumed all expansion opportunities. | 1070 } |
1082 // Failures here means that our logic does not match to the one in expansion OpportunityCount(). | 1071 |
1083 // FIXME: Ideally, we should ASSERT(!m_expansionOpportunityCount) here to en sure that, | 1072 currentFont = fallbackIterator->next(hint); |
1084 // or unify the two logics (and the one in SimplePath too,) but there are so me cases where our impl | 1073 if (!currentFont) { |
1085 // does not support justification very well yet such as U+3099, and it'll ca use the ASSERT to fail. | 1074 ASSERT(!m_holesQueue.size()); |
1086 // It's to be fixed because they're very rarely used, and a broken justifica tion is still somewhat readable. | 1075 break; |
1087 | 1076 } |
1077 fontCycleQueued = false; | |
1078 continue; | |
1079 } | |
1080 | |
1081 // TODO crbug.com/522964: Only use smallCapsFontData when the font d oes not support true smcp. The spec | |
1082 // says: "To match the surrounding text, a font may provide alternat e glyphs for caseless characters when | |
1083 // these features are enabled but when a user agent simulates small capitals, it must not attempt to | |
1084 // simulate alternates for codepoints which are considered caseless. " | |
1085 const SimpleFontData* smallcapsMangledFont = segmentRange.smallCapsB ehavior == SmallCapsIterator::SmallCapsUppercaseNeeded | |
1086 ? currentFont->smallCapsFontData(fontDescription).get() | |
1087 : currentFont; | |
1088 | |
1089 // Compatibility with SimpleFontData approach of keeping a flag for overriding drawing direction. | |
1090 // TODO: crbug.com/506224 This should go away in favor of storing th at information elsewhere, for example in | |
1091 // ShapeResult. | |
1092 const SimpleFontData* directionAndSmallCapsMangledFont = mangleFontD irection(smallcapsMangledFont, | |
1093 m_font->fontDescription().orientation(), | |
1094 segmentRange.renderOrientation); | |
1095 | |
1096 if (!shapeRange(harfBuzzBuffer.get(), | |
1097 currentQueueItem.m_startIndex, | |
1098 currentQueueItem.m_numCharacters, | |
1099 directionAndSmallCapsMangledFont, | |
1100 segmentRange.script, | |
1101 language)) | |
1102 WTF_LOG_ERROR("Shaping range failed."); | |
1103 | |
1104 if (!extractShapeResults(harfBuzzBuffer.get(), | |
1105 result.get(), | |
1106 &m_holesQueue, | |
1107 fontCycleQueued, | |
1108 currentQueueItem, | |
1109 directionAndSmallCapsMangledFont, | |
1110 segmentRange.script, | |
1111 !fallbackIterator->hasNext())) | |
1112 WTF_LOG_ERROR("Shape result extraction failed."); | |
1113 | |
1114 hb_buffer_reset(harfBuzzBuffer.get()); | |
1115 } | |
1116 } | |
1088 return result.release(); | 1117 return result.release(); |
1089 } | 1118 } |
1090 | 1119 |
1091 void HarfBuzzShaper::shapeResult(ShapeResult* result, unsigned index, | 1120 // TODO crbug.com/542701: This should be a method on ShapeResult. |
1092 const HarfBuzzRun* currentRun, hb_buffer_t* harfBuzzBuffer) | 1121 void HarfBuzzShaper::insertRunIntoShapeResult(ShapeResult* result, |
1093 { | 1122 PassOwnPtr<ShapeResult::RunInfo> runToInsert, int startGlyph, int numGlyphs, |
1094 unsigned numGlyphs = hb_buffer_get_length(harfBuzzBuffer); | 1123 int bufferStartCharIndex, hb_buffer_t* harfBuzzBuffer) |
1095 if (!numGlyphs) { | 1124 { |
1096 result->m_runs[index] = nullptr; | 1125 ASSERT(numGlyphs > 0); |
1097 return; | 1126 OwnPtr<ShapeResult::RunInfo> run(runToInsert); |
1098 } | 1127 |
1099 | 1128 const SimpleFontData* currentFontData = run->m_fontData.get(); |
1100 OwnPtr<ShapeResult::RunInfo> run = adoptPtr(new ShapeResult::RunInfo(current Run->m_fontData, | |
1101 currentRun->m_direction, currentRun->m_script, currentRun->m_startIndex, | |
1102 numGlyphs, currentRun->m_numCharacters)); | |
1103 | |
1104 const SimpleFontData* currentFontData = currentRun->m_fontData; | |
1105 hb_glyph_info_t* glyphInfos = hb_buffer_get_glyph_infos(harfBuzzBuffer, 0); | 1129 hb_glyph_info_t* glyphInfos = hb_buffer_get_glyph_infos(harfBuzzBuffer, 0); |
1106 hb_glyph_position_t* glyphPositions = hb_buffer_get_glyph_positions(harfBuzz Buffer, 0); | 1130 hb_glyph_position_t* glyphPositions = hb_buffer_get_glyph_positions(harfBuzz Buffer, 0); |
1107 | 1131 |
1108 float totalAdvance = 0.0f; | 1132 float totalAdvance = 0.0f; |
1109 FloatPoint glyphOrigin; | 1133 FloatPoint glyphOrigin; |
1110 float offsetX, offsetY; | 1134 float offsetX, offsetY; |
1111 float* directionOffset = m_font->fontDescription().isVerticalAnyUpright() ? &offsetY : &offsetX; | 1135 float* directionOffset = m_font->fontDescription().isVerticalAnyUpright() ? &offsetY : &offsetX; |
1112 | 1136 |
1113 // HarfBuzz returns result in visual order, no need to flip for RTL. | 1137 // HarfBuzz returns result in visual order, no need to flip for RTL. |
1114 for (size_t i = 0; i < numGlyphs; ++i) { | 1138 for (int i = 0; i < numGlyphs; ++i) { |
eae
2015/10/14 08:12:01
unsigned, pretty please?
| |
1115 bool runEnd = i + 1 == numGlyphs; | 1139 bool runEnd = i + 1 == numGlyphs; |
1116 uint16_t glyph = glyphInfos[i].codepoint; | 1140 uint16_t glyph = glyphInfos[startGlyph + i].codepoint; |
1117 offsetX = harfBuzzPositionToFloat(glyphPositions[i].x_offset); | 1141 offsetX = harfBuzzPositionToFloat(glyphPositions[startGlyph + i].x_offse t); |
1118 offsetY = -harfBuzzPositionToFloat(glyphPositions[i].y_offset); | 1142 offsetY = -harfBuzzPositionToFloat(glyphPositions[startGlyph + i].y_offs et); |
1119 | 1143 |
1120 // One out of x_advance and y_advance is zero, depending on | 1144 // One out of x_advance and y_advance is zero, depending on |
1121 // whether the buffer direction is horizontal or vertical. | 1145 // whether the buffer direction is horizontal or vertical. |
1122 float advance = harfBuzzPositionToFloat(glyphPositions[i].x_advance - gl yphPositions[i].y_advance); | 1146 float advance = harfBuzzPositionToFloat(glyphPositions[startGlyph + i].x _advance - glyphPositions[startGlyph + i].y_advance); |
1123 unsigned currentCharacterIndex = currentRun->m_startIndex + glyphInfos[i ].cluster; | 1147 unsigned currentCharacterIndex = bufferStartCharIndex + glyphInfos[start Glyph + i].cluster; |
1124 RELEASE_ASSERT(m_normalizedBufferLength > currentCharacterIndex); | 1148 RELEASE_ASSERT(m_normalizedBufferLength > currentCharacterIndex); |
1125 bool isClusterEnd = runEnd || glyphInfos[i].cluster != glyphInfos[i + 1] .cluster; | 1149 bool isClusterEnd = runEnd || glyphInfos[startGlyph + i].cluster != glyp hInfos[startGlyph + i + 1].cluster; |
1126 float spacing = 0; | 1150 float spacing = 0; |
1127 | 1151 |
1128 run->m_glyphData[i].characterIndex = glyphInfos[i].cluster; | 1152 // The characterIndex of one ShapeResult run is normalized to the run's |
1153 // startIndex and length. TODO crbug.com/542703: Consider changing that | |
1154 // and instead pass the whole run to hb_buffer_t each time. | |
1155 run->m_glyphData.resize(numGlyphs); | |
1156 if (HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harfBuzzBuffer))) { | |
1157 run->m_glyphData[i].characterIndex = glyphInfos[startGlyph + i].clus ter - glyphInfos[startGlyph].cluster; | |
1158 } else { | |
1159 run->m_glyphData[i].characterIndex = glyphInfos[startGlyph + i].clus ter - glyphInfos[startGlyph + numGlyphs - 1].cluster; | |
1160 } | |
1129 | 1161 |
1130 if (isClusterEnd) | 1162 if (isClusterEnd) |
1131 spacing += adjustSpacing(run.get(), i, currentCharacterIndex, *direc tionOffset, totalAdvance); | 1163 spacing += adjustSpacing(run.get(), i, currentCharacterIndex, *direc tionOffset, totalAdvance); |
1132 | 1164 |
1133 if (currentFontData->isZeroWidthSpaceGlyph(glyph)) { | 1165 if (currentFontData->isZeroWidthSpaceGlyph(glyph)) { |
1134 run->setGlyphAndPositions(i, glyph, 0, 0, 0); | 1166 run->setGlyphAndPositions(i, glyph, 0, 0, 0); |
1135 continue; | 1167 continue; |
1136 } | 1168 } |
1137 | 1169 |
1138 advance += spacing; | 1170 advance += spacing; |
1139 if (m_textRun.rtl()) { | 1171 if (m_textRun.rtl()) { |
1140 // In RTL, spacing should be added to left side of glyphs. | 1172 // In RTL, spacing should be added to left side of glyphs. |
1141 *directionOffset += spacing; | 1173 *directionOffset += spacing; |
1142 if (!isClusterEnd) | 1174 if (!isClusterEnd) |
1143 *directionOffset += m_letterSpacing; | 1175 *directionOffset += m_letterSpacing; |
1144 } | 1176 } |
1145 | 1177 |
1146 run->setGlyphAndPositions(i, glyph, advance, offsetX, offsetY); | 1178 run->setGlyphAndPositions(i, glyph, advance, offsetX, offsetY); |
1147 totalAdvance += advance; | 1179 totalAdvance += advance; |
1148 | 1180 |
1149 FloatRect glyphBounds = currentFontData->boundsForGlyph(glyph); | 1181 FloatRect glyphBounds = currentFontData->boundsForGlyph(glyph); |
1150 glyphBounds.move(glyphOrigin.x(), glyphOrigin.y()); | 1182 glyphBounds.move(glyphOrigin.x(), glyphOrigin.y()); |
1151 result->m_glyphBoundingBox.unite(glyphBounds); | 1183 result->m_glyphBoundingBox.unite(glyphBounds); |
1152 glyphOrigin += FloatSize(advance + offsetX, offsetY); | 1184 glyphOrigin += FloatSize(advance + offsetX, offsetY); |
1153 } | 1185 } |
1154 | |
1155 run->m_width = std::max(0.0f, totalAdvance); | 1186 run->m_width = std::max(0.0f, totalAdvance); |
1156 result->m_width += run->m_width; | 1187 result->m_width += run->m_width; |
1157 result->m_numGlyphs += numGlyphs; | 1188 result->m_numGlyphs += numGlyphs; |
1158 result->m_runs[index] = run.release(); | 1189 |
1190 // The runs are stored in result->m_runs in visual order. For LTR, we place | |
1191 // the run to be inserted before the next run with a bigger character | |
1192 // start index. For RTL, we place the run before the next run with a lower | |
1193 // character index. Otherwise, for both directions, at the end. | |
1194 if (HB_DIRECTION_IS_FORWARD(run->m_direction)) { | |
1195 for (size_t pos = 0; pos < result->m_runs.size(); ++pos) { | |
1196 if (result->m_runs.at(pos)->m_startIndex > run->m_startIndex) { | |
1197 result->m_runs.insert(pos, run.release()); | |
1198 break; | |
1199 } | |
1200 } | |
1201 } else { | |
1202 for (size_t pos = 0; pos < result->m_runs.size(); ++pos) { | |
1203 if (result->m_runs.at(pos)->m_startIndex < run->m_startIndex) { | |
1204 result->m_runs.insert(pos, run.release()); | |
1205 break; | |
1206 } | |
1207 } | |
1208 } | |
1209 // If we didn't find an existing slot to place it, append. | |
1210 if (run) { | |
1211 result->m_runs.append(run.release()); | |
1212 } | |
1159 } | 1213 } |
1160 | 1214 |
1161 PassRefPtr<ShapeResult> ShapeResult::createForTabulationCharacters(const Font* f ont, | 1215 PassRefPtr<ShapeResult> ShapeResult::createForTabulationCharacters(const Font* f ont, |
1162 const TextRun& textRun, float positionOffset, unsigned count) | 1216 const TextRun& textRun, float positionOffset, unsigned count) |
1163 { | 1217 { |
1164 const SimpleFontData* fontData = font->primaryFont(); | 1218 const SimpleFontData* fontData = font->primaryFont(); |
1165 OwnPtr<ShapeResult::RunInfo> run = adoptPtr(new ShapeResult::RunInfo(fontDat a, | 1219 OwnPtr<ShapeResult::RunInfo> run = adoptPtr(new ShapeResult::RunInfo(fontDat a, |
1166 // Tab characters are always LTR or RTL, not TTB, even when isVerticalAn yUpright(). | 1220 // Tab characters are always LTR or RTL, not TTB, even when isVerticalAn yUpright(). |
1167 textRun.rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, | 1221 textRun.rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, |
1168 HB_SCRIPT_COMMON, 0, count, count)); | 1222 HB_SCRIPT_COMMON, 0, count, count)); |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1232 if (!m_expansionOpportunityCount) | 1286 if (!m_expansionOpportunityCount) |
1233 return spacing; | 1287 return spacing; |
1234 } | 1288 } |
1235 | 1289 |
1236 // Don't need to check m_textRun.allowsTrailingExpansion() since it's covere d by !m_expansionOpportunityCount above | 1290 // Don't need to check m_textRun.allowsTrailingExpansion() since it's covere d by !m_expansionOpportunityCount above |
1237 spacing += nextExpansionPerOpportunity(); | 1291 spacing += nextExpansionPerOpportunity(); |
1238 m_isAfterExpansion = true; | 1292 m_isAfterExpansion = true; |
1239 return spacing; | 1293 return spacing; |
1240 } | 1294 } |
1241 | 1295 |
1296 | |
1242 } // namespace blink | 1297 } // namespace blink |
OLD | NEW |