1 /*
2  *
3  * Copyright 2019, 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 <teeui/label.h>
19 
20 #include <teeui/error.h>
21 #include <teeui/font_rendering.h>
22 
23 namespace teeui {
24 
draw(const PixelDrawer & drawPixel,const Box<pxs> & bounds,LineInfo * lineInfo)25 Error LabelImpl::draw(const PixelDrawer& drawPixel, const Box<pxs>& bounds, LineInfo* lineInfo) {
26     if (!font_) return Error::NotInitialized;
27 
28     Error error;
29     TextContext context;
30     std::tie(error, context) = TextContext::create();
31     if (error) return error;
32 
33     TextFace face;
34     std::tie(error, face) = context.loadFace(font_);
35     if (error) return error;
36 
37     error = face.setCharSizeInPix(fontSize());
38     if (error) return error;
39 
40     using intpxs = Coordinate<px, int64_t>;
41     Box<intpxs> intBounds(intpxs((int64_t)bounds.x().count()), intpxs((int64_t)bounds.y().count()),
42                           intpxs((int64_t)bounds.w().count()), intpxs((int64_t)bounds.h().count()));
43 
44     auto drawPixelBoundsEnforced =
45         makePixelDrawer([&, this](uint32_t x, uint32_t y, Color color) -> Error {
46             if (!intBounds.contains(Point<intpxs>(x, y))) {
47                 TEEUI_LOG << "Bounds: " << bounds << " Pixel: " << Point<pxs>(x, y) << ENDL;
48                 return Error::OutOfBoundsDrawing;
49             }
50             // combine the given alpha channel with the text color.
51             return drawPixel(x, y, (textColor_ & 0xffffff) | (color & 0xff000000));
52         });
53 
54 #ifdef DRAW_DEBUG_MARKERS
55     auto drawBox = [&](const Box<pxs>& box, Color c) {
56         for (int y = 0; y < box.h().count(); ++y) {
57             for (int x = 0; x < box.w().count(); ++x) {
58                 drawPixel(box.x().count() + x, box.y().count() + y, (c & 0xffffff) | 0x40000000);
59             }
60         }
61     };
62 
63     drawBox(bounds, 0xff);
64 #endif
65 
66     Point<pxs> pen = {0_px, 0_px};
67     auto textBegin = text_.begin();
68     optional<Box<pxs>> boundingBox;
69 
70     auto curLine = lineInfo->begin();
71     while (textBegin != text_.end()) {
72         if (curLine == lineInfo->end()) return Error::OutOfMemory;
73         Box<pxs> bBox;
74         std::tie(error, bBox, curLine->lineText) =
75             findLongestWordSequence(&face, text_t(*textBegin, *text_.end()), bounds);
76         if (error) return error;
77 
78         pen = {-bBox.x(), pen.y()};
79 
80         // check horizontal justification to set pen value
81         switch (horizontalTextAlignment_) {
82         case Alignment::LEFT:
83         case Alignment::TOP:
84         case Alignment::BOTTOM:
85             break;
86         case Alignment::CENTER:
87             pen += {(bounds.w() - bBox.w()) / 2.0_px, 0};
88             break;
89         case Alignment::RIGHT:
90             pen += {bounds.w() - bBox.w(), 0};
91             break;
92         }
93 
94         curLine->lineStart = pen;
95         bBox.translateSelf(pen);
96 
97         if (boundingBox)
98             boundingBox = boundingBox->merge(bBox);
99         else
100             boundingBox = bBox;
101 
102         textBegin = curLine->lineText.end();
103         pen += {0_px, lineHeight_};
104         ++curLine;
105     }
106 
107     if (!boundingBox) return Error::OK;
108 
109     TEEUI_LOG << "BoundingBox: " << *boundingBox << " Bounds: " << bounds << ENDL;
110     Point<pxs> offset = bounds.topLeft();
111     offset -= {0, boundingBox->y()};
112     TEEUI_LOG << "Offset: " << offset << ENDL;
113 
114     if (verticalTextAlignment_ == Alignment::CENTER)
115         offset += {0, (bounds.h() - boundingBox->h()) / 2.0_px};
116     else if (verticalTextAlignment_ == Alignment::BOTTOM)
117         offset += {0, (bounds.h() - boundingBox->h())};
118 
119     auto lineEnd = curLine;
120     curLine = lineInfo->begin();
121 
122 #ifdef DRAW_DEBUG_MARKERS
123     drawBox(boundingBox->translate(offset), 0xff00);
124     auto p = offset + curLine->lineStart;
125     drawPixel(p.x().count(), p.y().count(), 0xffff0000);
126 #endif
127 
128     while (curLine != lineEnd) {
129         if (auto error = drawText(&face, curLine->lineText, drawPixelBoundsEnforced,
130                                   curLine->lineStart + offset)) {
131             TEEUI_LOG << "drawText returned " << error << ENDL;
132             return error;
133         }
134         ++curLine;
135     }
136     return Error::OK;
137 }
138 
hit(const Event & event,const Box<pxs> & bounds)139 Error LabelImpl::hit(const Event& event, const Box<pxs>& bounds) {
140     using intpxs = Coordinate<px, int64_t>;
141     if (bounds.contains(Point<intpxs>(event.x_, event.y_))) {
142         optional<CallbackEvent> callback = getCB();
143         if (callback) {
144             return callback.value()(event);
145         }
146     }
147     return Error::OK;
148 }
149 }  // namespace teeui
150