Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright 2006 The Android Open Source Project
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkTextBox.h"
      9 #include "SkUtils.h"
     10 
     11 static inline int is_ws(int c)
     12 {
     13     return !((c - 1) >> 5);
     14 }
     15 
     16 static size_t linebreak(const char text[], const char stop[],
     17                         const SkPaint& paint, SkScalar margin,
     18                         size_t* trailing = nullptr)
     19 {
     20     size_t lengthBreak = paint.breakText(text, stop - text, margin);
     21 
     22     //Check for white space or line breakers before the lengthBreak
     23     const char* start = text;
     24     const char* word_start = text;
     25     int prevWS = true;
     26     if (trailing) {
     27         *trailing = 0;
     28     }
     29 
     30     while (text < stop) {
     31         const char* prevText = text;
     32         SkUnichar uni = SkUTF8_NextUnichar(&text);
     33         int currWS = is_ws(uni);
     34 
     35         if (!currWS && prevWS) {
     36             word_start = prevText;
     37         }
     38         prevWS = currWS;
     39 
     40         if (text > start + lengthBreak) {
     41             if (currWS) {
     42                 // eat the rest of the whitespace
     43                 while (text < stop && is_ws(SkUTF8_ToUnichar(text))) {
     44                     text += SkUTF8_CountUTF8Bytes(text);
     45                 }
     46                 if (trailing) {
     47                     *trailing = text - prevText;
     48                 }
     49             } else {
     50                 // backup until a whitespace (or 1 char)
     51                 if (word_start == start) {
     52                     if (prevText > start) {
     53                         text = prevText;
     54                     }
     55                 } else {
     56                     text = word_start;
     57                 }
     58             }
     59             break;
     60         }
     61 
     62         if ('\n' == uni) {
     63             size_t ret = text - start;
     64             size_t lineBreakSize = 1;
     65             if (text < stop) {
     66                 uni = SkUTF8_NextUnichar(&text);
     67                 if ('\r' == uni) {
     68                     ret = text - start;
     69                     ++lineBreakSize;
     70                 }
     71             }
     72             if (trailing) {
     73                 *trailing = lineBreakSize;
     74             }
     75             return ret;
     76         }
     77 
     78         if ('\r' == uni) {
     79             size_t ret = text - start;
     80             size_t lineBreakSize = 1;
     81             if (text < stop) {
     82                 uni = SkUTF8_NextUnichar(&text);
     83                 if ('\n' == uni) {
     84                     ret = text - start;
     85                     ++lineBreakSize;
     86                 }
     87             }
     88             if (trailing) {
     89                 *trailing = lineBreakSize;
     90             }
     91             return ret;
     92         }
     93     }
     94 
     95     return text - start;
     96 }
     97 
     98 int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width)
     99 {
    100     const char* stop = text + len;
    101     int         count = 0;
    102 
    103     if (width > 0)
    104     {
    105         do {
    106             count += 1;
    107             text += linebreak(text, stop, paint, width);
    108         } while (text < stop);
    109     }
    110     return count;
    111 }
    112 
    113 //////////////////////////////////////////////////////////////////////////////
    114 
    115 SkTextBox::SkTextBox()
    116 {
    117     fBox.setEmpty();
    118     fSpacingMul = SK_Scalar1;
    119     fSpacingAdd = 0;
    120     fMode = kLineBreak_Mode;
    121     fSpacingAlign = kStart_SpacingAlign;
    122 }
    123 
    124 void SkTextBox::setMode(Mode mode)
    125 {
    126     SkASSERT((unsigned)mode < kModeCount);
    127     fMode = SkToU8(mode);
    128 }
    129 
    130 void SkTextBox::setSpacingAlign(SpacingAlign align)
    131 {
    132     SkASSERT((unsigned)align < kSpacingAlignCount);
    133     fSpacingAlign = SkToU8(align);
    134 }
    135 
    136 void SkTextBox::getBox(SkRect* box) const
    137 {
    138     if (box)
    139         *box = fBox;
    140 }
    141 
    142 void SkTextBox::setBox(const SkRect& box)
    143 {
    144     fBox = box;
    145 }
    146 
    147 void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
    148 {
    149     fBox.set(left, top, right, bottom);
    150 }
    151 
    152 void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
    153 {
    154     if (mul)
    155         *mul = fSpacingMul;
    156     if (add)
    157         *add = fSpacingAdd;
    158 }
    159 
    160 void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
    161 {
    162     fSpacingMul = mul;
    163     fSpacingAdd = add;
    164 }
    165 
    166 /////////////////////////////////////////////////////////////////////////////////////////////
    167 
    168 SkScalar SkTextBox::visit(Visitor& visitor, const char text[], size_t len,
    169                           const SkPaint& paint) const {
    170     SkScalar marginWidth = fBox.width();
    171 
    172     if (marginWidth <= 0 || len == 0) {
    173         return fBox.top();
    174     }
    175 
    176     const char* textStop = text + len;
    177 
    178     SkScalar                x, y, scaledSpacing, height, fontHeight;
    179     SkPaint::FontMetrics    metrics;
    180 
    181     switch (paint.getTextAlign()) {
    182     case SkPaint::kLeft_Align:
    183         x = 0;
    184         break;
    185     case SkPaint::kCenter_Align:
    186         x = SkScalarHalf(marginWidth);
    187         break;
    188     default:
    189         x = marginWidth;
    190         break;
    191     }
    192     x += fBox.fLeft;
    193 
    194     fontHeight = paint.getFontMetrics(&metrics);
    195     scaledSpacing = fontHeight * fSpacingMul + fSpacingAdd;
    196     height = fBox.height();
    197 
    198     //  compute Y position for first line
    199     {
    200         SkScalar textHeight = fontHeight;
    201 
    202         if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign) {
    203             int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth);
    204             SkASSERT(count > 0);
    205             textHeight += scaledSpacing * (count - 1);
    206         }
    207 
    208         switch (fSpacingAlign) {
    209         case kStart_SpacingAlign:
    210             y = 0;
    211             break;
    212         case kCenter_SpacingAlign:
    213             y = SkScalarHalf(height - textHeight);
    214             break;
    215         default:
    216             SkASSERT(fSpacingAlign == kEnd_SpacingAlign);
    217             y = height - textHeight;
    218             break;
    219         }
    220         y += fBox.fTop - metrics.fAscent;
    221     }
    222 
    223     for (;;) {
    224         size_t trailing;
    225         len = linebreak(text, textStop, paint, marginWidth, &trailing);
    226         if (y + metrics.fDescent + metrics.fLeading > 0) {
    227             visitor(text, len - trailing, x, y, paint);
    228         }
    229         text += len;
    230         if (text >= textStop) {
    231             break;
    232         }
    233         y += scaledSpacing;
    234         if (y + metrics.fAscent >= fBox.fBottom) {
    235             break;
    236         }
    237     }
    238     return y + metrics.fDescent + metrics.fLeading;
    239 }
    240 
    241 ///////////////////////////////////////////////////////////////////////////////
    242 
    243 class CanvasVisitor : public SkTextBox::Visitor {
    244     SkCanvas* fCanvas;
    245 public:
    246     CanvasVisitor(SkCanvas* canvas) : fCanvas(canvas) {}
    247 
    248     void operator()(const char text[], size_t length, SkScalar x, SkScalar y,
    249                     const SkPaint& paint) override {
    250         fCanvas->drawText(text, length, x, y, paint);
    251     }
    252 };
    253 
    254 void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) {
    255     fText = text;
    256     fLen = len;
    257     fPaint = &paint;
    258 }
    259 
    260 void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint) {
    261     CanvasVisitor sink(canvas);
    262     this->visit(sink, text, len, paint);
    263 }
    264 
    265 void SkTextBox::draw(SkCanvas* canvas) {
    266     this->draw(canvas, fText, fLen, *fPaint);
    267 }
    268 
    269 int SkTextBox::countLines() const {
    270     return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width());
    271 }
    272 
    273 SkScalar SkTextBox::getTextHeight() const {
    274     SkScalar spacing = fPaint->getTextSize() * fSpacingMul + fSpacingAdd;
    275     return this->countLines() * spacing;
    276 }
    277 
    278 ///////////////////////////////////////////////////////////////////////////////
    279 
    280 #include "SkTextBlob.h"
    281 
    282 class TextBlobVisitor : public SkTextBox::Visitor {
    283 public:
    284     SkTextBlobBuilder fBuilder;
    285 
    286     void operator()(const char text[], size_t length, SkScalar x, SkScalar y,
    287                     const SkPaint& paint) override {
    288         SkPaint p(paint);
    289         p.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
    290         const int count = paint.countText(text, length);
    291         paint.textToGlyphs(text, length, fBuilder.allocRun(p, count, x, y).glyphs);
    292     }
    293 };
    294 
    295 sk_sp<SkTextBlob> SkTextBox::snapshotTextBlob(SkScalar* computedBottom) const {
    296     TextBlobVisitor visitor;
    297     SkScalar newB = this->visit(visitor, fText, fLen, *fPaint);
    298     if (computedBottom) {
    299         *computedBottom = newB;
    300     }
    301     return visitor.fBuilder.make();
    302 }
    303