Home | History | Annotate | Download | only in views
      1 /* libs/graphics/views/SkTextBox.cpp
      2 **
      3 ** Copyright 2006, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 ** See the License for the specific language governing permissions and
     15 ** limitations under the License.
     16 */
     17 
     18 #include "SkTextBox.h"
     19 #include "../src/core/SkGlyphCache.h"
     20 #include "SkUtils.h"
     21 #include "SkAutoKern.h"
     22 
     23 static inline int is_ws(int c)
     24 {
     25     return !((c - 1) >> 5);
     26 }
     27 
     28 static size_t linebreak(const char text[], const char stop[], const SkPaint& paint, SkScalar margin)
     29 {
     30     const char* start = text;
     31 
     32     SkAutoGlyphCache    ac(paint, NULL);
     33     SkGlyphCache*       cache = ac.getCache();
     34     SkFixed             w = 0;
     35     SkFixed             limit = SkScalarToFixed(margin);
     36     SkAutoKern          autokern;
     37 
     38     const char* word_start = text;
     39     int         prevWS = true;
     40 
     41     while (text < stop)
     42     {
     43         const char* prevText = text;
     44         SkUnichar   uni = SkUTF8_NextUnichar(&text);
     45         int         currWS = is_ws(uni);
     46         const SkGlyph&  glyph = cache->getUnicharMetrics(uni);
     47 
     48         if (!currWS && prevWS)
     49             word_start = prevText;
     50         prevWS = currWS;
     51 
     52         w += autokern.adjust(glyph) + glyph.fAdvanceX;
     53         if (w > limit)
     54         {
     55             if (currWS) // eat the rest of the whitespace
     56             {
     57                 while (text < stop && is_ws(SkUTF8_ToUnichar(text)))
     58                     text += SkUTF8_CountUTF8Bytes(text);
     59             }
     60             else    // backup until a whitespace (or 1 char)
     61             {
     62                 if (word_start == start)
     63                 {
     64                     if (prevText > start)
     65                         text = prevText;
     66                 }
     67                 else
     68                     text = word_start;
     69             }
     70             break;
     71         }
     72     }
     73     return text - start;
     74 }
     75 
     76 int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width)
     77 {
     78     const char* stop = text + len;
     79     int         count = 0;
     80 
     81     if (width > 0)
     82     {
     83         do {
     84             count += 1;
     85             text += linebreak(text, stop, paint, width);
     86         } while (text < stop);
     87     }
     88     return count;
     89 }
     90 
     91 //////////////////////////////////////////////////////////////////////////////
     92 
     93 SkTextBox::SkTextBox()
     94 {
     95     fBox.setEmpty();
     96     fSpacingMul = SK_Scalar1;
     97     fSpacingAdd = 0;
     98     fMode = kLineBreak_Mode;
     99     fSpacingAlign = kStart_SpacingAlign;
    100 }
    101 
    102 void SkTextBox::setMode(Mode mode)
    103 {
    104     SkASSERT((unsigned)mode < kModeCount);
    105     fMode = SkToU8(mode);
    106 }
    107 
    108 void SkTextBox::setSpacingAlign(SpacingAlign align)
    109 {
    110     SkASSERT((unsigned)align < kSpacingAlignCount);
    111     fSpacingAlign = SkToU8(align);
    112 }
    113 
    114 void SkTextBox::getBox(SkRect* box) const
    115 {
    116     if (box)
    117         *box = fBox;
    118 }
    119 
    120 void SkTextBox::setBox(const SkRect& box)
    121 {
    122     fBox = box;
    123 }
    124 
    125 void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
    126 {
    127     fBox.set(left, top, right, bottom);
    128 }
    129 
    130 void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
    131 {
    132     if (mul)
    133         *mul = fSpacingMul;
    134     if (add)
    135         *add = fSpacingAdd;
    136 }
    137 
    138 void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
    139 {
    140     fSpacingMul = mul;
    141     fSpacingAdd = add;
    142 }
    143 
    144 /////////////////////////////////////////////////////////////////////////////////////////////
    145 
    146 void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint)
    147 {
    148     SkASSERT(canvas && &paint && (text || len == 0));
    149 
    150     SkScalar marginWidth = fBox.width();
    151 
    152     if (marginWidth <= 0 || len == 0)
    153         return;
    154 
    155     const char* textStop = text + len;
    156 
    157     SkScalar                x, y, scaledSpacing, height, fontHeight;
    158     SkPaint::FontMetrics    metrics;
    159 
    160     switch (paint.getTextAlign()) {
    161     case SkPaint::kLeft_Align:
    162         x = 0;
    163         break;
    164     case SkPaint::kCenter_Align:
    165         x = SkScalarHalf(marginWidth);
    166         break;
    167     default:
    168         x = marginWidth;
    169         break;
    170     }
    171     x += fBox.fLeft;
    172 
    173     fontHeight = paint.getFontMetrics(&metrics);
    174     scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd;
    175     height = fBox.height();
    176 
    177     //  compute Y position for first line
    178     {
    179         SkScalar textHeight = fontHeight;
    180 
    181         if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign)
    182         {
    183             int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth);
    184             SkASSERT(count > 0);
    185             textHeight += scaledSpacing * (count - 1);
    186         }
    187 
    188         switch (fSpacingAlign) {
    189         case kStart_SpacingAlign:
    190             y = 0;
    191             break;
    192         case kCenter_SpacingAlign:
    193             y = SkScalarHalf(height - textHeight);
    194             break;
    195         default:
    196             SkASSERT(fSpacingAlign == kEnd_SpacingAlign);
    197             y = height - textHeight;
    198             break;
    199         }
    200         y += fBox.fTop - metrics.fAscent;
    201     }
    202 
    203     for (;;)
    204     {
    205         len = linebreak(text, textStop, paint, marginWidth);
    206         if (y + metrics.fDescent + metrics.fLeading > 0)
    207             canvas->drawText(text, len, x, y, paint);
    208         text += len;
    209         if (text >= textStop)
    210             break;
    211         y += scaledSpacing;
    212         if (y + metrics.fAscent >= height)
    213             break;
    214     }
    215 }
    216 
    217