root/font/font_layout.cc

Revision 65:4416cfac86ae, 18.3 KB (checked in by Thibaut Girka <thib@…>, 18 months ago)
Convert EUC-JP files to UTF8
Line 
1/*  layout2.cc
2 *     ãƒ†ã‚­ã‚¹ãƒˆã®çŠå‰‡å‡Šç†ã€ãƒ¬ã‚€ã‚¢ã‚Šãƒˆãªã©ã‚’è¡Œã†
3 */
4/*
5 * Copyright (c) 2004-2006  Kazunori "jagarl" Ueno
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote products
17 *    derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include <vector>
32#include <map>
33#include <iostream>
34
35using namespace std;
36
37#include "font.h"
38#include "text.h"
39
40const int line_skip = 1; // 行ず行の間の間隔
41const int ruby_textskip = 0; // 文字ずルビの間の間隔
42const int ruby_lineskip = 1; // ルビがあるずきに行間に加える倀
43const double ruby_scale = 0.4; // ルビのスケヌル
44
45class TextGlyphStreamHelper;
46
47enum KinsokuType { KinsokuHead = 1, KinsokuTail = 2};
48static int kinsoku_table1[] = {
49/* 0 1 2 3 4 5 6 7     0 1 2 3 4 5 6 7 */
50   0,0,2,2,2,2,0,0, /*  、。・ */
51   0,2,2,0,0,0,0,0, /* ゛゜Žš */
52   0,0,0,0,0,0,0,0, /*  ̄ボミゝゞ〃 */
53   0,0,0,0,2,0,0,0, /* 仝々〆〇ヌ―‐ */
54   0,2,0,0,2,2,1,2, /* 〜‖ ‥‘’ */
55   1,2,1,2,1,2,1,2, /* “”〔〕 */
56   1,2,1,2,1,2,1,2, /* 〈〉《》「」 */
57   1,2,1,2,0,0,0,0, /* 『』【】−±× */
58   0,0,0,0,0,0,0,0, /* ÷≠≊≧∞ */
59   0,0,0,0,0,0,0,0, /* ∎♂♀°′″℃¥ */
60   0,0,0,0,0,0,0,0, /* ¢£ */
61   0,0,0,0,0,0,0,0, /* §☆★○●◎◇ */
62   0
63};
64static int kinsoku_table2[] = {
65  0,2,0,2,0,2,0,2,0,2,0,0,0,0,0,0, /*  ぁあぃいぅうぇえぉおかがきぎく */
66  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ぐけげこごさざしじすずせぜそぞた */
67  0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, /* だちぢっ぀づおでずどなにぬねのは */
68  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ばぱひびぎふぶぷぞべぺほがぜたみ */
69  0,0,0,2,0,2,0,2,0,0,0,0,0,0,2,0, /* むめもゃやゅゆょよらりるれろゎわ */
70  0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0, /* ゐゑをんノヵヶ */
71  0
72};
73
74inline int Kinsoku(int code) {
75        if ( (code&0xff80) == 0xa180) return kinsoku_table1[ (code&0xff) - 0xa0];
76        if ( (code&0xfe80) == 0xa480) return kinsoku_table2[ (code&0xff) - 0xa0]; /* code = 0xa400 / 0xa500 */
77        return 0;
78}
79
80class TextGlyphStreamHelper {
81        private:
82                typedef TextStream::Iterator Iterator;
83                typedef TextGlyphStream::iterator iterator;
84
85                TextGlyphStream* data;
86
87                // information for rendering
88                unsigned char r, g, b;
89                XKFont::Face* face;
90                XKFont::Face* ruby_face;
91                XKFont::Font* font;
92
93        public:
94                int min_lineheight;
95                TextGlyphStreamHelper(XKFont::Font* font);
96                // helper functions
97                void Init(TextGlyphStream* data);
98                Iterator Add(int& x, Iterator begin, Iterator end, int max_x = 0);
99                Iterator AddRuby(int& x, Iterator begin, Iterator end);
100                int CharWidth(int code);
101                void SetGroup(iterator begin, iterator end);
102                void CalcHeight(int& ascent, int& descent, iterator begin, iterator end);
103                void AdjustPosition(int xstart_add, int xend_add, int y_add, iterator begin, iterator end);
104};
105
106TextGlyphStreamHelper::TextGlyphStreamHelper(XKFont::Font* __font) {
107        font = __font;
108        face = font->FaceLoad(1.0);
109        ruby_face = 0;
110        r = 255; g = 255; b = 255;
111        min_lineheight = font->vsize;
112}
113
114void TextGlyphStreamHelper::Init(TextGlyphStream* __data) {
115        r = 255; g = 255; b = 255;
116        face = font->FaceLoad(1.0);
117        data = __data;
118        data->clear();
119        data->font = font;
120}
121
122TextGlyphStreamHelper::Iterator
123TextGlyphStreamHelper::Add(int& x, TextGlyphStreamHelper::Iterator begin, TextGlyphStreamHelper::Iterator end, int max_x) {
124        /* text を glyph に倉換する */
125        TextGlyph gl;
126        Iterator it;
127        gl.x = x; gl.y = 0; gl.r = r; gl.g = g; gl.b = b; gl.flag = TextGlyph::Flag(0); gl.is_rev = false;
128        for (it = begin; it != end; it++) {
129                if (it->type != TextElem::glyph) {
130                        if (it->type == TextElem::color) {
131                                gl.r = r = it->impl.Color.r;
132                                gl.g = g = it->impl.Color.g;
133                                gl.b = b = it->impl.Color.b;
134                        } else if (it->type == TextElem::size) {
135                                delete face;
136                                face = font->FaceLoad(it->impl.Size.scale);
137                        } else if (it->type == TextElem::escape) {
138                                x = gl.x;
139                                return it;
140                        }
141                        continue;
142                }
143                try {
144                        gl.glyph = face->GlyphLoad(it->impl.Glyph.code);
145                        if (max_x > 0 && gl.x + gl.glyph->advance.x > max_x) {
146                                x = gl.x;
147                                return it;
148                        }
149                        if ( Kinsoku(it->impl.Glyph.code) == KinsokuTail)
150                                gl.flag = TextGlyph::Flag(gl.flag | TextGlyph::Kinsoku);
151                        else
152                                gl.flag = TextGlyph::Flag(0);
153                        data->push_back(gl);
154                        gl.x += gl.glyph->advance.x;
155                } catch(...) {}
156        }
157        x = gl.x;
158        return it;
159}
160
161TextGlyphStreamHelper::Iterator TextGlyphStreamHelper::AddRuby(int& x, TextGlyphStreamHelper::Iterator sbegin, TextGlyphStreamHelper::Iterator send) {
162        Iterator it;
163        it = sbegin;
164        if (it == send) return it;
165        if (it->type != TextElem::escape || it->impl.Escape.type != TextElem::ruby_start) return sbegin;
166        it++;
167        /* たず、本文描画 */
168        int str_firstpos = data->size();
169        int str_width = 0;
170        it = Add(str_width, it, send);
171        if (it == send || it->type != TextElem::escape || it->impl.Escape.type != TextElem::ruby_startruby) {
172                // ありえないはずだが、取り合えずなにもしないで終了
173                cerr << "TextGlyphStream::AddRuby : invalid operation; fallback to the upeer level"<<endl;
174                data->erase(data->begin()+str_firstpos, data->end());
175                return sbegin+1;
176        }
177        it++;
178        int str_lastpos = data->size()-1;
179        TextGlyph& str_first = data->begin()[str_firstpos];
180        TextGlyph& str_last = data->back();
181        // 次に、フォントを取りかえおルビ描画
182        int ruby_firstpos = data->size();
183        XKFont::Face* save_font = face;
184        if (ruby_face == 0) ruby_face = font->FaceLoad(ruby_scale);
185        face = ruby_face;
186        int ruby_width = 0;
187        it = Add(ruby_width, it, send);
188        if (it->type != TextElem::escape || it->impl.Escape.type != TextElem::ruby_end) {
189                /* ありえないはずだが、取り合えずなにもしないで終了 */
190                cerr << "TextGlyphStream::AddRuby : invalid operation; fallback to the upeer level"<<endl;
191                data->erase(data->begin()+str_firstpos, data->end());
192                return sbegin+1;
193        }
194        it++;
195        face = save_font;
196        TextGlyph& ruby_first = (*data)[ruby_firstpos];
197        TextGlyph& ruby_last = data->back();
198
199        /* ルビを移動すべき高さを求める */
200        int dummy, str_ascent, ruby_descent;
201        CalcHeight(str_ascent, dummy, data->begin()+str_firstpos, data->begin()+ruby_firstpos);
202        CalcHeight(dummy, ruby_descent, data->begin()+ruby_firstpos, data->end());
203        int ruby_height = str_ascent + ruby_descent + ruby_textskip;
204
205        /* センタリングした堎合の、ルビの巊偎、右偎のマヌゞン */
206        int leftmergin, rightmergin;
207        leftmergin = str_first.glyph->advance.x/2 - (ruby_first.glyph->advance.x+1)/2;
208        rightmergin = str_last.glyph->advance.x/2 - (ruby_last.glyph->advance.x+1)/2;
209
210        /* ルビ、本文の暪方向の移動 */
211        int ruby_xstart_add = 0, ruby_xend_add = 0, str_xstart_add=0, str_xend_add = 0;
212        if (ruby_width+leftmergin+rightmergin <= str_width) { // ルビの方が小さい
213                ruby_xstart_add = leftmergin;
214                ruby_xend_add = str_width-rightmergin-ruby_width;
215        } else if (ruby_width <= str_width) { // マヌゞンを枛らす必芁あり
216                leftmergin = (str_width-ruby_width)/2;
217                ruby_xstart_add = leftmergin;
218                ruby_xend_add = str_width-leftmergin-ruby_width;
219        } else { // ルビの方が倧きい
220                int str_count = ruby_firstpos - str_firstpos;
221                str_xstart_add = ruby_width/str_count/2 - str_first.glyph->advance.x/2;
222                str_xend_add = (ruby_width-str_width) - (ruby_width/str_count/2-str_last.glyph->advance.x/2);
223                str_width = ruby_width;
224        }
225        AdjustPosition(str_xstart_add+x, str_xend_add+x, 0, data->begin()+str_firstpos, data->begin()+ruby_firstpos);
226        AdjustPosition(ruby_xstart_add+x, ruby_xend_add+x, -ruby_height, data->begin()+ruby_firstpos, data->end());
227
228        /* 本文が䞀文字ず぀衚瀺されるように glyph の順番を入れかえ、グルヌプ化 */
229        vector<TextGlyph> save;
230        save.assign(data->begin()+str_firstpos, data->end());
231        iterator it_str = save.begin();
232        iterator it_ruby = save.begin()+(ruby_firstpos-str_firstpos);
233        iterator dit = data->begin()+str_firstpos;
234        int str_count = it_ruby-it_str;
235        int ruby_count = save.end()-it_ruby;
236        int i,j = 0;
237        for (i=0; i<str_count; i++) {
238                iterator charstart = dit;
239                int jend = (i+1)*ruby_count/str_count;
240                for (; j<jend; j++) {
241                        *dit++ = *it_ruby++;
242                }
243                *dit++ = *it_str++;
244                SetGroup(charstart, dit);
245        }
246        x += str_width;
247        return it;
248}
249
250
251void TextGlyphStreamHelper::SetGroup(TextGlyphStreamHelper::iterator begin, TextGlyphStreamHelper::iterator end) {
252        iterator it;
253        for (it = begin; it+1 != end; it++)
254                it->flag = TextGlyph::Flag(it->flag |TextGlyph::Group);
255        it->flag = TextGlyph::Flag(it->flag & ~TextGlyph::Group);
256        return;
257}
258
259void TextGlyphStreamHelper::AdjustPosition(int xstart_add, int xend_add, int y_add, TextGlyphStreamHelper::iterator begin, TextGlyphStreamHelper::iterator end) {
260        iterator it;
261        /* 文字数を数える */
262        int total_count = 0;
263        for (it = begin; it != end; it++) {
264                if (it->flag & TextGlyph::Group) continue;
265                total_count++;
266        }
267        /* 文字間のギャップを倉曎 */
268        int incr = 0;
269        if (total_count != 1) incr = (xend_add - xstart_add) * 256 / (total_count-1);
270        int cur = xstart_add * 256;
271        for (it = begin; it != end; it++) {
272                it->x += cur / 256;
273                it->y += y_add;
274                if (it->flag & TextGlyph::Group) continue;
275                cur += incr;
276        }
277        return;
278}
279void TextGlyphStreamHelper::CalcHeight(int& ascent_r, int& descent_r, TextGlyphStreamHelper::iterator begin, TextGlyphStreamHelper::iterator end) {
280        iterator it;
281        /* 最倧の descent, ascent を蚈算 */
282        int ascent = 0;
283        int descent = 0;
284        for (it = begin; it != end; it++) {
285
286                int y_top = it->y - it->glyph->bitmap_top;
287                int y_bottom = it->y + it->glyph->bitmap.rows - it->glyph->bitmap_top;
288
289                if (descent < y_bottom) descent = y_bottom;
290                if (ascent < -y_top) ascent = -y_top;
291        }
292        ascent_r = ascent;
293        descent_r = descent;
294        return;
295}
296
297int TextGlyphStreamHelper::CharWidth(int code) {
298        try {
299                XKFont::Glyph* g = face->GlyphLoad(code);
300                return g->advance.x;
301        } catch(...) {
302                return 0;
303        }
304}
305
306class TextHorizLayout {
307        typedef TextStream::Iterator Iterator;
308
309        Iterator pos;
310        Iterator end;
311        TextGlyphStream* data;
312        TextGlyphStreamHelper helper;
313        int tab_width;
314        int cur_y;
315
316        void SetName(void);
317        void SetLineHead(void);
318        void MakeLine(int line_first, int width, vector<int>& lineheights);
319public:
320        TextHorizLayout(XKFont::Font* font);
321        void Layout(TextStream& stream, TextGlyphStream& glyph, vector<int>& lineheights, int width);
322};
323
324TextHorizLayout::TextHorizLayout(XKFont::Font* font) :
325        helper(font), tab_width(0), cur_y(0) {
326}
327
328void TextHorizLayout::Layout(TextStream& stream, TextGlyphStream& glyph, vector<int>& lineheights, int width) {
329        pos = stream.container.begin();
330        end = stream.container.end();
331        data = &glyph;
332
333        helper.Init(data);
334        tab_width = 0;
335        cur_y = 0;
336        int prev_y = 0;
337        int line_start = glyph.size();
338        while(pos != end) {
339/*
340if (pos->type == TextElem::glyph) { int c = pos->impl.Glyph.code; char cc[3]={0,0,0};cc[0]=c>>8;cc[1]=c;cout<<"glyph "<<cc<<endl;}
341if (pos->type == TextElem::escape) { cout<<"escape "<<pos->impl.Escape.type<<endl;}
342*/
343                SetName();
344                SetLineHead();
345                MakeLine(line_start, width, lineheights);
346                if (line_start != glyph.size()) {
347                        data->back().flag = TextGlyph::Flag(data->back().flag | TextGlyph::PhraseEnd | TextGlyph::LineEnd);
348                }
349                prev_y = cur_y;
350                if (pos != end && pos->type == TextElem::escape && pos->impl.Escape.type == TextElem::ret) pos++;
351                line_start = glyph.size();
352        }
353        return;
354}
355
356void TextHorizLayout::SetName(void) {
357        Iterator it;
358
359        tab_width = 0;
360        /* 行頭が名前なら、凊理開始 */
361        for (; pos != end; pos++) {
362                if (pos->type == TextElem::escape || pos->type == TextElem::glyph) break;
363                int x = 0;
364                helper.Add(x, pos, pos+1);
365        }
366
367        if (pos->type != TextElem::escape || pos->impl.Escape.type != TextElem::name_start) return;
368
369        /* 名前をセットし、行頭の「の分を含めおタブ幅を蚭定する */
370        pos++;
371        for (it = pos; it != end; it++) {
372                if (it->type == TextElem::escape && it->impl.Escape.type == TextElem::name_end) break;
373        }
374        if (it == end) return;
375        int line_firstpos = data->size();
376        pos = helper.Add(tab_width, pos, it);
377        pos++;
378        helper.SetGroup(data->begin() + line_firstpos, data->end());
379
380        // 行頭の「分を開ける
381        try {
382                tab_width += helper.CharWidth(0xa1d6); /* 「 */
383        } catch(...) {}
384       
385        return;
386};
387
388void TextHorizLayout::SetLineHead(void) {
389
390        /* 行頭は 「などか */
391
392        for (; pos != end; pos++) {
393                if (pos->type == TextElem::escape || pos->type == TextElem::glyph) break;
394                int x = 0;
395                helper.Add(x, pos, pos+1);
396        }
397        if (pos->type != TextElem::glyph || Kinsoku(pos->impl.Glyph.code) != KinsokuHead) return;
398
399        /* 「なので、凊理する */
400        if (tab_width != 0) tab_width -= helper.CharWidth(pos->impl.Glyph.code);
401        int line_firstpos = data->size();
402        pos = helper.Add(tab_width, pos, pos+1);
403        return;
404}
405
406void TextHorizLayout::MakeLine(int line_start, int width, vector<int>& lineheights) {
407       
408        int x = tab_width;
409        /* たず、党文字描画する */
410        while(pos != end) {
411                pos = helper.Add(x, pos, end);
412                if (pos->type == TextElem::escape && pos->impl.Escape.type == TextElem::ruby_start) { 
413                        pos = helper.AddRuby(x, pos, end);
414                }
415                if (pos != end && pos->type == TextElem::escape) {
416                        if (pos->impl.Escape.type == TextElem::ret) break;
417                        if (pos->impl.Escape.type != TextElem::ruby_start) pos++;
418                }
419        }
420        /* 行に分割しおいく */
421        TextGlyphStream::iterator it_start = data->begin() + line_start;
422        TextGlyphStream::iterator it_end = data->end();
423        TextGlyphStream::iterator it = it_start;
424
425        TextGlyphStream::iterator group_head = it_start;
426        int xstart = tab_width;
427        int xend = width;
428        while(it != it_end) {
429                // この行の終わりを決める
430                bool is_ruby = false;
431                TextGlyphStream::iterator it_line_start = it;
432                for (; it != it_end; it++) {
433                        if (it->x + it->glyph->advance.x > xend) break;
434                        if (it->flag & TextGlyph::Group) is_ruby = true;
435                        if (!(it->flag & TextGlyph::Group)) group_head = it;
436                }
437                // 氎平移動の倧きさを決める。デフォルトでタブ䜍眮たで戻す
438                int xadd_start = -xstart + tab_width;
439                int xadd_end = xadd_start;
440                // it == 次行の先頭なので、今行の末尟ぞ戻す
441                // ただし、 最䜎䞀文字の衚瀺は保蚌
442                if (it != it_line_start && it != it_line_start+1 && it != it_end) it--;
443                if (it != it_end) {
444                        // グルヌプ化されおいる文字で終了したら、前の文字に戻す
445                        if (it->flag & TextGlyph::Group) it = group_head;
446                        // 次が行頭犁則文字ならこの行に入れる
447                        if ( (it+1) != it_end && (it+1)->flag & TextGlyph::Kinsoku) it++;
448                        // 移動する倧きさを決める
449                        // 行端ぞろえ、行末文字なら半文字分だけ突き出る
450                        int glyph_xend = it->x + it->glyph->advance.x;
451                        if (it != it_line_start && (it-1)->flag & TextGlyph::Group) { // グルヌプ化文字の堎合、文字前も芋る
452                                if (glyph_xend < (it-1)->x + (it-1)->glyph->advance.x)
453                                        glyph_xend = (it-1)->x + (it-1)->glyph->advance.x;
454                        }
455                        xadd_end += xend - glyph_xend;
456                        if (it->flag & TextGlyph::Kinsoku)
457                                xadd_end += it->glyph->advance.x / 2;
458                }
459                if (it != it_end) {
460                        it->flag = TextGlyph::Flag(it->flag | TextGlyph::LineEnd);
461                        it++; // it == 次行の先頭ぞ
462                }
463                int ascent, descent;
464                helper.CalcHeight(ascent, descent, it_start, it);
465                if (ascent+descent < helper.min_lineheight) {
466                        int dif = helper.min_lineheight-(ascent+descent);
467                        ascent += dif/2;
468                        descent += dif-(dif/2);
469                }
470                if (is_ruby) ascent+=ruby_lineskip;
471                helper.AdjustPosition(xadd_start, xadd_end, cur_y+ascent+1, it_start, it);
472                cur_y += ascent + descent + line_skip;
473                lineheights.push_back(ascent+descent+line_skip);
474
475                /* 次の行ぞ */
476                if (it != it_end) {
477                        it_start = it;
478                        group_head = it_start;
479                        /* 文字目がグルヌプ化されおいれば、グルヌプの先頭文字にする */
480                        xstart = it->x;
481                        if (it->flag & TextGlyph::Group) {
482                                TextGlyphStream::iterator jit;
483                                for (jit = it; jit != it_end; jit++) {
484                                        if (xstart > jit->x) xstart = jit->x;
485                                        if (!(jit->flag & TextGlyph::Group)) break;
486                                }
487                        }
488                        xend = it->x + width-tab_width;
489                }
490        }
491        return;
492}
493
494namespace XKFont {
495
496        HorizLayout::HorizLayout(const char* fontname, int size) {
497                font = new Font(fontname, size);
498                pimpl = new ::TextHorizLayout(font);
499        }
500
501        HorizLayout::~HorizLayout() {
502                delete pimpl;
503                delete font;
504        }
505
506        void HorizLayout::Layout(TextStream& stream, TextGlyphStream& glyph, vector<int>& lineheights, int width) {
507                pimpl->Layout(stream, glyph, lineheights, width);
508        };
509
510        TextGlyphStream HorizLayout::Layout(const char* str, int width, int r, int gc, int b) {
511                TextStream s;
512                s.SetColor(r,gc,b);
513                s.Add(str);
514                return Layout(s, width);
515        }
516
517        TextGlyphStream HorizLayout::Layout(TextStream s, int width) {
518                TextGlyphStream g;
519                vector<int> h;
520                Layout(s, g, h, width);
521                return g;
522        }
523
524};
525
526int TextGlyphStream::width(void) {
527        if (empty()) return 0;
528        iterator it;
529        int xmax = 0;
530        for (it=begin(); it!=end(); it++) {
531                int x = it->x + it->glyph->advance.x;
532                if (x > xmax) xmax = x;
533        }
534        return xmax + 1;
535}
536
537int TextGlyphStream::height(void) {
538        if (empty()) return 0;
539        iterator it;
540        int ymax = 0;
541        it = end();
542        while(1) {
543                it--;
544                int y = it->y + it->glyph->bitmap.rows - it->glyph->bitmap_top;
545                if (ymax < y) ymax = y;
546                if (it == begin()) break;
547                if (it->flag & TextGlyph::LineEnd) {
548                        if (!(it->flag & TextGlyph::PhraseEnd)) break; // PhraseEnd は最埌の文字
549                }
550        }
551        return ymax + 1;
552}
553
Note: See TracBrowser for help on using the browser.