| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871 |
- // Copyright (C) 2004-2025 Artifex Software, Inc.
- //
- // This file is part of MuPDF.
- //
- // MuPDF is free software: you can redistribute it and/or modify it under the
- // terms of the GNU Affero General Public License as published by the Free
- // Software Foundation, either version 3 of the License, or (at your option)
- // any later version.
- //
- // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
- // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
- // details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
- //
- // Alternative licensing terms are available from the licensor.
- // For commercial licensing, see <https://www.artifex.com/> or contact
- // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
- // CA 94129, USA, for further information.
- #include "mupdf/fitz.h"
- #include "mupdf/ucdn.h"
- #include "html-imp.h"
- #include "hb.h"
- #include "hb-ft.h"
- #include <ft2build.h>
- #include <math.h>
- #include <assert.h>
- #undef DEBUG_HARFBUZZ
- /*
- Some notes on the layout code below and the concepts used.
- THE MODEL:
- The standard CSS box model is used here. Each box has a margin, a border, padding, and content, any of which can be zero in size.
- +-------------------------------------------------------------------+
- | margin |
- | +-------------------------------------------------+ |
- | | border | |
- | | +-------------------------------+ | |
- | | | padding | | |
- | | | +---------+ | | |
- | | | | content | | | |
- | | | +---------+ | | |
- | | | | | |
- | | +-------------------------------+ | |
- | | | |
- | +-------------------------------------------------+ |
- | |
- +-------------------------------------------------------------------+
- In our structures, the box->x,y,w,b refer to the bounding box of the content section alone. The margins/borders/paddings are applied
- to the outside of this automatically.
- HOW WE LAYOUT:
- Our implementation is a 'simple' recursive descent of the structure. We know the width of the area we are laying out into at each point,
- and we know the top/left coords of the enclosing block. So we start with a well defined left/top point, offset that for margins/borders/padding
- etc, calculate the width from the enclosing width (via the CSS). We set the height of the content to be zero to start with, and after laying
- out each of (any) child elements, we ensure that the base of our box is always large enough to enclose the child's boxes with the appropriate
- padding.
- VERTICAL MARGIN COLLAPSE:
- The big complexity in this code is the need to do 'vertical margin collapse'. Basically, if we have 2 blocks, laid out vertically from one
- another, the margin between them is collapsed to be the larger of margins of the two blocks, rather than the sum of the margins of the
- two blocks.
- So block A with a margin of 30, and block B with a margin of 20 will be separated by 30, not 50.
- To make matters more complicated, margin collapse can happen 'through' nestings of blocks, and even through empty blocks. It cannot
- happen through blocks with borders, or padding.
- Accordingly, as we recursively pass through our structure laying out, we keep a 'vertical' parameter, representing the amount of vertical
- space to be considered for collapse. This is set to zero if we hit borders or padding or non-zero height content boxes, and our margins are
- adjusted by it.
- While the margins of a block may be adjusted by a parent 'inheriting' those margins, the bbox of the content of a block will never be
- changed.
- NON-RESTARTING OPERATION:
- The standard layout operation as used for HTML documents and ebooks has us layout onto an infinitely long page, with the vertical
- offsets of boxes adjusted so that a given line of text (or image) never spans a page end (defined to be a multiple of page_h). This
- layout can proceed from start to finish with no need to stop. This is achieved by calling the code with restart == NULL.
- RESTARTING:
- In order to cope with laying out a text story into multiple (potentially differently sized) areas, we need to be able to stop the layout
- when the available area is full, and restart again later. We do this by having a restart record. The tree structure means we can't
- simply return a pointer to the node to continue processing at - we need to recursively redescend the tree until we reach the same
- position, whereupon we will continue. Also, because of the fact that the area being laid out into is potentially a completely different
- size, we need to recalculate widths etc, so at least part of the tree needs to be reprocessed when we restart.
- Accordingly, two ways to do this immediately suggest themselves. The first is to store the path down the tree so that we can quickly
- and efficiently skip whole areas of the tree that do not need reprocessing and redescend just the sections we need to. The second is to
- accept that we will reprocess the entire prefix of the tree each time, and just not actually do any work until we reach the exact
- box where we need to restart.
- We opt for the second one here, on the grounds that 1) it's easier to code, 2) it requires a constant amount of storage, and 3) that
- the trees we will be reprocessing will be relatively small. Should we ever need to revisit this decision, we can do so.
- */
- enum { T, R, B, L };
- /* === LAYOUT INLINE TEXT === */
- typedef struct string_walker
- {
- fz_context *ctx;
- hb_buffer_t *hb_buf;
- int rtl;
- const char *start;
- const char *end;
- const char *s;
- fz_font *base_font;
- int script;
- int language;
- int small_caps;
- fz_font *font;
- fz_font *next_font;
- hb_glyph_position_t *glyph_pos;
- hb_glyph_info_t *glyph_info;
- unsigned int glyph_count;
- int scale;
- } string_walker;
- static int quick_ligature_mov(fz_context *ctx, string_walker *walker, unsigned int i, unsigned int n, int unicode)
- {
- unsigned int k;
- for (k = i + n + 1; k < walker->glyph_count; ++k)
- {
- walker->glyph_info[k-n] = walker->glyph_info[k];
- walker->glyph_pos[k-n] = walker->glyph_pos[k];
- }
- walker->glyph_count -= n;
- return unicode;
- }
- static int quick_ligature(fz_context *ctx, string_walker *walker, unsigned int i)
- {
- if (walker->glyph_info[i].codepoint == 'f' && i + 1 < walker->glyph_count && !fz_font_flags(walker->font)->is_mono)
- {
- if (walker->glyph_info[i+1].codepoint == 'f')
- {
- if (i + 2 < walker->glyph_count && walker->glyph_info[i+2].codepoint == 'i')
- {
- if (fz_encode_character(ctx, walker->font, 0xFB03))
- return quick_ligature_mov(ctx, walker, i, 2, 0xFB03);
- }
- if (i + 2 < walker->glyph_count && walker->glyph_info[i+2].codepoint == 'l')
- {
- if (fz_encode_character(ctx, walker->font, 0xFB04))
- return quick_ligature_mov(ctx, walker, i, 2, 0xFB04);
- }
- if (fz_encode_character(ctx, walker->font, 0xFB00))
- return quick_ligature_mov(ctx, walker, i, 1, 0xFB00);
- }
- if (walker->glyph_info[i+1].codepoint == 'i')
- {
- if (fz_encode_character(ctx, walker->font, 0xFB01))
- return quick_ligature_mov(ctx, walker, i, 1, 0xFB01);
- }
- if (walker->glyph_info[i+1].codepoint == 'l')
- {
- if (fz_encode_character(ctx, walker->font, 0xFB02))
- return quick_ligature_mov(ctx, walker, i, 1, 0xFB02);
- }
- }
- return walker->glyph_info[i].codepoint;
- }
- static void init_string_walker(fz_context *ctx, string_walker *walker, hb_buffer_t *hb_buf, int rtl, fz_font *font, int script, int language, int small_caps, const char *text)
- {
- walker->ctx = ctx;
- walker->hb_buf = hb_buf;
- walker->rtl = rtl;
- walker->start = text;
- walker->end = text;
- walker->s = text;
- walker->base_font = font;
- walker->script = script;
- walker->language = language;
- walker->font = NULL;
- walker->next_font = NULL;
- walker->small_caps = small_caps;
- }
- static void
- destroy_hb_shaper_data(fz_context *ctx, void *handle)
- {
- fz_hb_lock(ctx);
- hb_font_destroy(handle);
- fz_hb_unlock(ctx);
- }
- static const hb_feature_t small_caps_feature[1] = {
- { HB_TAG('s','m','c','p'), 1, 0, (unsigned int)-1 }
- };
- static int walk_string(string_walker *walker)
- {
- fz_context *ctx = walker->ctx;
- FT_Face face;
- int fterr;
- int quickshape;
- char lang[8];
- walker->start = walker->end;
- walker->end = walker->s;
- walker->font = walker->next_font;
- if (*walker->start == 0)
- return 0;
- /* Run through the string, encoding chars until we find one
- * that requires a different fallback font. */
- while (*walker->s)
- {
- int c;
- walker->s += fz_chartorune(&c, walker->s);
- (void)fz_encode_character_with_fallback(ctx, walker->base_font, c, walker->script, walker->language, &walker->next_font);
- if (walker->next_font != walker->font)
- {
- if (walker->font != NULL)
- break;
- walker->font = walker->next_font;
- }
- walker->end = walker->s;
- }
- /* Disable harfbuzz shaping if script is common or LGC and there are no opentype tables. */
- quickshape = 0;
- if (walker->script <= 3 && !walker->rtl && !fz_font_flags(walker->font)->has_opentype)
- quickshape = 1;
- fz_hb_lock(ctx);
- fz_try(ctx)
- {
- face = fz_font_ft_face(ctx, walker->font);
- walker->scale = face->units_per_EM;
- fterr = FT_Set_Char_Size(face, walker->scale, walker->scale, 72, 72);
- if (fterr)
- fz_throw(ctx, FZ_ERROR_LIBRARY, "freetype setting character size: %s", ft_error_string(fterr));
- hb_buffer_clear_contents(walker->hb_buf);
- hb_buffer_set_direction(walker->hb_buf, walker->rtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
- /* hb_buffer_set_script(walker->hb_buf, hb_ucdn_script_translate(walker->script)); */
- if (walker->language)
- {
- fz_string_from_text_language(lang, walker->language);
- Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
- hb_buffer_set_language(walker->hb_buf, hb_language_from_string(lang, (int)strlen(lang)));
- Memento_stopLeaking(); /* HarfBuzz leaks harmlessly */
- }
- hb_buffer_set_cluster_level(walker->hb_buf, HB_BUFFER_CLUSTER_LEVEL_CHARACTERS);
- hb_buffer_add_utf8(walker->hb_buf, walker->start, walker->end - walker->start, 0, -1);
- if (!quickshape)
- {
- fz_shaper_data_t *hb = fz_font_shaper_data(ctx, walker->font);
- Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
- if (hb->shaper_handle == NULL)
- {
- hb->destroy = destroy_hb_shaper_data;
- hb->shaper_handle = hb_ft_font_create(face, NULL);
- }
- hb_buffer_guess_segment_properties(walker->hb_buf);
- if (walker->small_caps)
- hb_shape(hb->shaper_handle, walker->hb_buf, small_caps_feature, nelem(small_caps_feature));
- else
- hb_shape(hb->shaper_handle, walker->hb_buf, NULL, 0);
- Memento_stopLeaking();
- }
- walker->glyph_pos = hb_buffer_get_glyph_positions(walker->hb_buf, &walker->glyph_count);
- walker->glyph_info = hb_buffer_get_glyph_infos(walker->hb_buf, NULL);
- }
- fz_always(ctx)
- {
- fz_hb_unlock(ctx);
- }
- fz_catch(ctx)
- {
- fz_rethrow(ctx);
- }
- if (quickshape)
- {
- unsigned int i;
- for (i = 0; i < walker->glyph_count; ++i)
- {
- int glyph, unicode;
- unicode = quick_ligature(ctx, walker, i);
- if (walker->small_caps)
- glyph = fz_encode_character_sc(ctx, walker->font, unicode);
- else
- glyph = fz_encode_character(ctx, walker->font, unicode);
- walker->glyph_info[i].codepoint = glyph;
- walker->glyph_pos[i].x_offset = 0;
- walker->glyph_pos[i].y_offset = 0;
- walker->glyph_pos[i].x_advance = fz_advance_glyph(ctx, walker->font, glyph, 0) * face->units_per_EM;
- walker->glyph_pos[i].y_advance = 0;
- }
- }
- return 1;
- }
- static const char *get_node_text(fz_context *ctx, fz_html_flow *node)
- {
- if (node->type == FLOW_WORD)
- return node->content.text;
- else if (node->type == FLOW_SPACE)
- return " ";
- else if (node->type == FLOW_SHYPHEN)
- return "-";
- else
- return "";
- }
- static void measure_string_w(fz_context *ctx, fz_html_flow *node, hb_buffer_t *hb_buf)
- {
- float em = node->box->s.layout.em;
- string_walker walker;
- unsigned int i;
- const char *s;
- node->w = 0;
- s = get_node_text(ctx, node);
- init_string_walker(ctx, &walker, hb_buf, node->bidi_level & 1, node->box->style->font, node->script, node->markup_lang, node->box->style->small_caps, s);
- while (walk_string(&walker))
- {
- int x = 0;
- for (i = 0; i < walker.glyph_count; i++)
- x += walker.glyph_pos[i].x_advance;
- node->w += x * em / walker.scale;
- }
- }
- static void measure_string_h(fz_context *ctx, fz_html_flow *node)
- {
- float em = node->box->s.layout.em;
- if (fz_css_number_defined(node->box->style->leading))
- node->h = fz_from_css_number(node->box->style->leading, em, em, 0);
- else
- node->h = fz_from_css_number_scale(node->box->style->line_height, em);
- }
- static float measure_line(fz_html_flow *node, fz_html_flow *end, float *baseline)
- {
- float max_a = 0, max_d = 0, h = node->h;
- while (node != end)
- {
- if (node->type == FLOW_IMAGE)
- {
- if (node->h > max_a)
- max_a = node->h;
- }
- else if (node->type != FLOW_SBREAK && node->type != FLOW_BREAK)
- {
- // Clamp ascender/descender to line-height size.
- // TODO: This is not entirely to spec, but close enough.
- float s = fz_min(node->box->s.layout.em, node->h);
- float a = s * 0.8f;
- float d = s * 0.2f;
- if (a > max_a) max_a = a;
- if (d > max_d) max_d = d;
- }
- if (node->h > h)
- h = node->h;
- node = node->next;
- }
- *baseline = max_a + (h - max_a - max_d) / 2;
- return h;
- }
- static void layout_line(fz_context *ctx, float indent, float page_w, float line_w, int align, fz_html_flow *start, fz_html_flow *end, fz_html_box *box, float baseline, float line_h)
- {
- float x = box->s.layout.x + indent;
- float y = box->s.layout.b;
- float slop = page_w - line_w;
- float justify = 0;
- float va;
- int n, i;
- fz_html_flow *node;
- fz_html_flow **reorder;
- unsigned int min_level, max_level;
- /* Count the number of nodes on the line */
- for(i = 0, n = 0, node = start; node != end; node = node->next)
- {
- n++;
- if (node->type == FLOW_SPACE && node->expand && !node->breaks_line)
- i++;
- }
- if (align == TA_JUSTIFY)
- {
- justify = slop / (i ? i : 1);
- }
- else if (align == TA_RIGHT)
- x += slop;
- else if (align == TA_CENTER)
- x += slop / 2;
- /* We need a block to hold the node pointers while we reorder */
- reorder = fz_malloc_array(ctx, n, fz_html_flow*);
- min_level = start->bidi_level;
- max_level = start->bidi_level;
- for(i = 0, node = start; node != end; i++, node = node->next)
- {
- reorder[i] = node;
- if (node->bidi_level < min_level)
- min_level = node->bidi_level;
- if (node->bidi_level > max_level)
- max_level = node->bidi_level;
- }
- /* Do we need to do any reordering? */
- if (min_level != max_level || (min_level & 1))
- {
- /* The lowest level we swap is always a rtl one */
- min_level |= 1;
- /* Each time around the loop we swap runs of fragments that have
- * levels >= max_level (and decrement max_level). */
- do
- {
- int start_idx = 0;
- int end_idx;
- do
- {
- /* Skip until we find a level that's >= max_level */
- while (start_idx < n && reorder[start_idx]->bidi_level < max_level)
- start_idx++;
- /* If start >= n-1 then no more runs. */
- if (start_idx >= n-1)
- break;
- /* Find the end of the match */
- i = start_idx+1;
- while (i < n && reorder[i]->bidi_level >= max_level)
- i++;
- /* Reverse from start to i-1 */
- end_idx = i-1;
- while (start_idx < end_idx)
- {
- fz_html_flow *t = reorder[start_idx];
- reorder[start_idx++] = reorder[end_idx];
- reorder[end_idx--] = t;
- }
- start_idx = i+1;
- }
- while (start_idx < n);
- max_level--;
- }
- while (max_level >= min_level);
- }
- for (i = 0; i < n; i++)
- {
- float w;
- node = reorder[i];
- w = node->w;
- if (node->type == FLOW_SPACE && node->breaks_line)
- w = 0;
- else if (node->type == FLOW_SPACE && !node->breaks_line)
- w += node->expand ? justify : 0;
- else if (node->type == FLOW_SHYPHEN && !node->breaks_line)
- w = 0;
- else if (node->type == FLOW_SHYPHEN && node->breaks_line)
- w = node->w;
- node->x = x;
- x += w;
- switch (node->box->style->vertical_align)
- {
- default:
- case VA_BASELINE:
- case VA_SUB:
- case VA_SUPER:
- va = node->box->s.layout.baseline;
- break;
- case VA_TOP:
- case VA_TEXT_TOP:
- va = -baseline + node->box->s.layout.em * 0.8f;
- break;
- case VA_BOTTOM:
- case VA_TEXT_BOTTOM:
- va = -baseline + line_h - node->box->s.layout.em * 0.2f;
- break;
- }
- if (node->type == FLOW_IMAGE)
- node->y = y + baseline - node->h;
- else
- {
- node->y = y + baseline + va;
- node->h = node->box->s.layout.em;
- }
- }
- fz_free(ctx, reorder);
- }
- typedef struct
- {
- fz_pool *pool;
- float page_top;
- float page_h;
- hb_buffer_t *hb_buf;
- fz_html_restarter *restart;
- } layout_data;
- static int flush_line(fz_context *ctx, fz_html_box *box, layout_data *ld, float page_w, float line_w, int align, float indent, fz_html_flow *a, fz_html_flow *b, fz_html_restarter *restart)
- {
- float avail, line_h, baseline;
- float page_h = ld->page_h;
- float page_top = ld->page_top;
- line_h = measure_line(a, b, &baseline);
- if (page_h > 0)
- {
- avail = page_h - fmodf(box->s.layout.b - page_top, page_h);
- /* If the line is larger than the available space skip to the start
- * of the next page. */
- if (line_h > avail)
- {
- if (restart)
- {
- assert(restart->start == NULL);
- if (restart->potential)
- restart->end = restart->potential;
- else
- {
- restart->end = box;
- restart->end_flow = a;
- }
- restart->reason = FZ_HTML_RESTART_REASON_LINE_HEIGHT;
- return 1;
- }
- box->s.layout.b += avail;
- }
- }
- layout_line(ctx, indent, page_w, line_w, align, a, b, box, baseline, line_h);
- box->s.layout.b += line_h;
- if (restart)
- restart->potential = NULL;
- return 0;
- }
- static void break_word_for_overflow_wrap(fz_context *ctx, fz_html_flow *node, layout_data *ld)
- {
- hb_buffer_t *hb_buf = ld->hb_buf;
- const char *text = node->content.text;
- string_walker walker;
- assert(node->type == FLOW_WORD);
- assert(node->atomic == 0);
- /* Split a word node after the first cluster (usually a character), and
- * flag the second half as a valid node to break before if in desperate
- * need. This may break earlier than necessary, but in that case we'll
- * break the second half again when we come to it, until we find a
- * suitable breaking point.
- *
- * We split after each clusters here so we can flag each fragment as
- * "atomic" so we don't try breaking it again, and also to flag the
- * following word fragment as a possible break point. Breaking at the
- * exact desired point would make this more complicated than necessary.
- *
- * Desperately breaking in the middle of a word like this should should
- * rarely (if ever) come up.
- *
- * TODO: Split at all the clusters in the word at once.
- */
- /* Walk string and split at the first cluster. */
- init_string_walker(ctx, &walker, hb_buf, node->bidi_level & 1, node->box->style->font, node->script, node->markup_lang, node->box->style->small_caps, text);
- while (walk_string(&walker))
- {
- unsigned int i, a, b;
- a = walker.glyph_info[0].cluster;
- for (i = 0; i < walker.glyph_count; ++i)
- {
- b = walker.glyph_info[i].cluster;
- if (b != a)
- {
- fz_html_split_flow(ctx, ld->pool, node, fz_runeidx(text, text + b));
- node->atomic = 1;
- node->next->overflow_wrap = 1;
- measure_string_w(ctx, node, ld->hb_buf);
- measure_string_w(ctx, node->next, ld->hb_buf);
- return;
- }
- }
- }
- /* Word is already only one cluster. Don't try breaking here again! */
- node->atomic = 1;
- }
- /*
- Layout a BOX_FLOW.
- Flow box is in a BOX_BLOCK or BOX_TABLE_CELL context, and has no margin/padding/border.
- box: The BOX_FLOW to layout.
- top: The enclosing box.
- */
- static void layout_flow(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top)
- {
- fz_html_flow *node, *line, *candidate, *desperate;
- fz_html_flow *start_flow = NULL;
- float line_w, candidate_w, desperate_w, indent;
- int align;
- fz_html_restarter *restart = ld->restart;
- float em = box->s.layout.em;
- indent = box->is_first_flow ? fz_from_css_number(top->style->text_indent, em, top->s.layout.w, 0) : 0;
- align = top->style->text_align;
- if (box->markup_dir == FZ_BIDI_RTL)
- {
- if (align == TA_LEFT)
- align = TA_RIGHT;
- else if (align == TA_RIGHT)
- align = TA_LEFT;
- }
- /* Position the box, initially zero height. */
- box->s.layout.x = top->s.layout.x;
- box->s.layout.y = top->s.layout.b;
- box->s.layout.w = top->s.layout.w;
- box->s.layout.b = box->s.layout.y;
- if (restart && restart->start)
- {
- /* If we are still skipping, and don't match, nothing to do. */
- if (restart->start != box)
- return;
- /* We match! Remember where we should start. */
- restart->start = NULL;
- #ifdef DEBUG_LAYOUT_RESTARTING
- /* Output us some crufty debug */
- if (restart->start_flow == NULL)
- printf("<restart>");
- for (node = box->u.flow.head; node; node = node->next)
- {
- if (restart->start_flow == node)
- printf("<restart>");
- switch (node->type)
- {
- case FLOW_WORD:
- printf("[%s]", node->content.text);
- break;
- case FLOW_BREAK:
- case FLOW_SBREAK:
- printf("\n");
- break;
- case FLOW_SPACE:
- printf(" ");
- break;
- case FLOW_SHYPHEN:
- printf("-");
- break;
- default:
- printf("?");
- break;
- }
- }
- printf("\n");
- #endif
- }
- /* If we have nothing to flow, nothing to do. */
- if (!box->u.flow.head)
- return;
- /* Measure the size of all the words and images in the flow. */
- node = box->u.flow.head;
- /* First, if we are restarting, skip over ones we've done already. */
- if (restart && restart->start_flow)
- {
- while(node && node != restart->start_flow)
- {
- indent = 0;
- node = node->next;
- }
- start_flow = node;
- restart->start_flow = NULL;
- }
- /* Now measure the size of the remaining nodes. */
- for (; node; node = node->next)
- {
- node->breaks_line = 0; /* reset line breaks from previous layout */
- if (node->type == FLOW_IMAGE)
- {
- float max_w, max_h;
- float xs = 1, ys = 1, s;
- float aspect = 1;
- max_w = top->s.layout.w;
- max_h = ld->page_h;
- /* NOTE: We ignore the image DPI here, since most images in EPUB files have bogus values. */
- node->w = node->content.image->w * 72.0f / 96.0f;
- node->h = node->content.image->h * 72.0f / 96.0f;
- aspect = node->h ? node->w / node->h : 0;
- if (node->box->style->width.unit != N_AUTO)
- node->w = fz_from_css_number(node->box->style->width, top->s.layout.em, top->s.layout.w, node->w);
- if (node->box->style->height.unit != N_AUTO)
- node->h = fz_from_css_number(node->box->style->height, top->s.layout.em, ld->page_h, node->h);
- if (node->box->style->width.unit == N_AUTO && node->box->style->height.unit != N_AUTO)
- node->w = node->h * aspect;
- if (node->box->style->width.unit != N_AUTO && node->box->style->height.unit == N_AUTO)
- node->h = (aspect == 0) ? 0 : (node->w / aspect);
- /* Shrink image to fit on one page if needed */
- if (max_w > 0 && node->w > max_w)
- xs = max_w / node->w;
- if (max_h > 0 && node->h > max_h)
- ys = max_h / node->h;
- s = fz_min(xs, ys);
- node->w = node->w * s;
- node->h = node->h * s;
- }
- else
- {
- /* Note: already measured width in layout_update_widths */
- measure_string_h(ctx, node);
- }
- }
- node = box->u.flow.head;
- if (start_flow)
- {
- line_w = start_flow == node ? indent : 0;
- node = start_flow;
- }
- else
- {
- line_w = indent;
- }
- line = node;
- candidate = NULL;
- candidate_w = 0;
- desperate = NULL;
- desperate_w = 0;
- /* Now collate the measured nodes into a line. */
- while (node)
- {
- switch (node->type)
- {
- default:
- // always same width
- line_w += node->w;
- break;
- case FLOW_WORD:
- /* Allow desperate breaking in middle of word if overflow-wrap: break-word. */
- if (line_w + node->w > box->s.layout.w && !candidate)
- {
- if (!node->atomic && node->box->style->overflow_wrap == OVERFLOW_WRAP_BREAK_WORD)
- {
- break_word_for_overflow_wrap(ctx, node, ld);
- }
- }
- /* Remember overflow-wrap word fragments, unless at the beginning of a line. */
- if (node->overflow_wrap && node != line)
- {
- desperate = node;
- desperate_w = line_w;
- }
- line_w += node->w;
- break;
- case FLOW_BREAK:
- case FLOW_SBREAK:
- // always zero width
- candidate = node;
- candidate_w = line_w;
- break;
- case FLOW_SPACE:
- // zero width if broken, default width if unbroken
- candidate = node;
- candidate_w = line_w;
- line_w += node->w;
- break;
- case FLOW_SHYPHEN:
- // default width if broken, zero width if unbroken
- candidate = node;
- candidate_w = line_w + node->w;
- break;
- }
- /* If we see a hard break...
- * or the line doesn't fit, and we have a candidate breakpoint...
- * then we can flush the line so far. */
- if (node->type == FLOW_BREAK || (line_w > box->s.layout.w && (candidate || desperate)))
- {
- int line_align = align;
- fz_html_flow *break_at;
- float break_w;
- if (candidate)
- {
- if (candidate->type == FLOW_BREAK)
- line_align = (align == TA_JUSTIFY) ? TA_LEFT : align;
- candidate->breaks_line = 1;
- break_at = candidate->next;
- break_w = candidate_w;
- }
- else
- {
- break_at = desperate;
- break_w = desperate_w;
- }
- if (flush_line(ctx, box, ld, box->s.layout.w, break_w, line_align, indent, line, break_at, restart))
- return;
- line = break_at;
- node = break_at;
- candidate = NULL;
- candidate_w = 0;
- desperate = NULL;
- desperate_w = 0;
- indent = 0;
- line_w = 0;
- }
- else if (restart && (restart->flags & FZ_HTML_RESTARTER_FLAGS_NO_OVERFLOW) && line_w > box->s.layout.w)
- {
- /* We are in 'no-overflow' mode, and the line doesn't fit.
- * This means our first box is too wide to ever fit in a box of this width. */
- assert(restart->start == NULL);
- restart->end = box;
- restart->end_flow = start_flow;
- restart->reason = FZ_HTML_RESTART_REASON_LINE_WIDTH;
- return;
- }
- else
- {
- node = node->next;
- }
- }
- if (line)
- {
- int line_align = (align == TA_JUSTIFY) ? TA_LEFT : align;
- flush_line(ctx, box, ld, box->s.layout.w, line_w, line_align, indent, line, NULL, restart);
- }
- }
- static float advance_for_spacing(fz_context *ctx, layout_data *ld, float start_b, float spacing, int *eop)
- {
- float page_h = ld->page_h;
- float page_top = ld->page_top;
- float avail = page_h - fmodf(start_b - page_top, page_h);
- if (spacing > avail)
- {
- *eop = 1;
- spacing = avail;
- }
- return start_b + spacing;
- }
- static int layout_block_page_break(fz_context *ctx, layout_data *ld, float *yp, int page_break)
- {
- float page_h = ld->page_h;
- float page_top = ld->page_top;
- if (page_h <= 0)
- return 0;
- if (page_break == PB_ALWAYS || page_break == PB_LEFT || page_break == PB_RIGHT)
- {
- float avail = page_h - fmodf(*yp - page_top, page_h);
- int number = (*yp + (page_h * 0.1f)) / page_h;
- if (avail > 0 && avail < page_h)
- {
- *yp += avail;
- if (page_break == PB_LEFT && (number & 1) == 0) /* right side pages are even */
- *yp += page_h;
- if (page_break == PB_RIGHT && (number & 1) == 1) /* left side pages are odd */
- *yp += page_h;
- return 1;
- }
- }
- return 0;
- }
- static void layout_block(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top);
- static void layout_table(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top);
- /* === LAYOUT TABLE === */
- // TODO: apply CSS from colgroup and col definition to table cells
- // TODO: use CSS border-collapse on table
- // TODO: use CSS/HTML column-span/colspan on table-cell
- // TODO: use CSS/HTML width on table-cell when computing maximum width
- struct column_width {
- float min, max, actual;
- };
- static float table_cell_padding(fz_context *ctx, fz_html_box *box)
- {
- return (
- box->u.block.padding[L] + box->u.block.border[L] +
- box->u.block.padding[R] + box->u.block.border[R]
- );
- }
- static float block_padding(fz_context *ctx, fz_html_box *box)
- {
- return (
- box->u.block.padding[L] + box->u.block.border[L] + box->u.block.margin[L] +
- box->u.block.padding[R] + box->u.block.border[R] + box->u.block.margin[R]
- );
- }
- static float largest_min_width(fz_context *ctx, fz_html_box *box)
- {
- /* The "minimum" width is the largest word in a paragraph. */
- float r_min = 0;
- if (box->type == BOX_BLOCK)
- {
- fz_html_box *child = box->down;
- while (child)
- {
- float min = largest_min_width(ctx, child);
- if (min > r_min)
- r_min = min;
- child = child->next;
- }
- r_min += block_padding(ctx, box);
- }
- else if (box->type == BOX_FLOW)
- {
- fz_html_flow *flow;
- for (flow = box->u.flow.head; flow; flow = flow->next)
- if (flow->w > r_min)
- r_min = flow->w;
- }
- else
- {
- // TODO: nested TABLE
- }
- return r_min;
- }
- static float largest_max_width(fz_context *ctx, fz_html_box *box)
- {
- /* The "maximum" width is the length of the longest paragraph laid out on one line. */
- float r_max = 0;
- if (box->type == BOX_BLOCK)
- {
- fz_html_box *child = box->down;
- while (child)
- {
- float max = largest_max_width(ctx, child);
- if (max > r_max)
- r_max = max;
- child = child->next;
- }
- r_max += block_padding(ctx, box);
- }
- else if (box->type == BOX_FLOW)
- {
- fz_html_flow *flow;
- float max = 0;
- for (flow = box->u.flow.head; flow; flow = flow->next)
- {
- max += flow->w;
- if (flow->type == FLOW_BREAK)
- {
- if (max > r_max)
- r_max = max;
- max = 0;
- }
- }
- if (max > r_max)
- r_max = max;
- }
- else
- {
- // TODO: nested TABLE
- }
- return r_max;
- }
- static void squish_block(fz_context *ctx, fz_html_box *box)
- {
- fz_html_box *child;
- box->s.layout.b = box->s.layout.y;
- if (box->type == BOX_FLOW)
- return;
- for (child = box->down; child; child = child->next)
- squish_block(ctx, child);
- }
- static void layout_table_row(fz_context *ctx, layout_data *ld, fz_html_box *row, int ncol, struct column_width *colw, float spacing)
- {
- fz_html_box *cell, *child;
- int col = 0;
- float x = row->s.layout.x;
- float y;
- /* Always layout the full row since we can't restart in the middle of a cell.
- * If the row doesn't fit fully, we'll postpone it to the next page.
- * FIXME: If the row doesn't fit then either, we should split it with the offset.
- */
- fz_html_restarter *save_restart = ld->restart;
- ld->restart = NULL;
- /* Note: margin is ignored for table cells and rows */
- /* For each cell in the row */
- for (cell = row->down; cell; cell = cell->next)
- {
- float cell_pad = table_cell_padding(ctx, cell);
- x += spacing;
- /* Position the cell */
- cell->s.layout.y = row->s.layout.y;
- cell->s.layout.x = x;
- cell->s.layout.w = colw[col].actual - cell_pad;
- /* Adjust content box */
- cell->s.layout.x += cell->u.block.padding[L] + cell->u.block.border[L];
- cell->s.layout.y += cell->u.block.padding[T] + cell->u.block.border[T];
- cell->s.layout.b = cell->s.layout.y;
- /* Layout cell contents into the cell. */
- for (child = cell->down; child; child = child->next)
- {
- if (child->type == BOX_BLOCK)
- {
- layout_block(ctx, ld, child, cell);
- cell->s.layout.b = child->s.layout.b;
- cell->s.layout.b += child->u.block.padding[B] + child->u.block.border[B] + child->u.block.margin[B];
- }
- else if (child->type == BOX_TABLE)
- {
- layout_table(ctx, ld, child, cell);
- cell->s.layout.b = child->s.layout.b;
- cell->s.layout.b += child->u.block.padding[B] + child->u.block.border[B] + child->u.block.margin[B];
- }
- else if (child->type == BOX_FLOW)
- {
- layout_flow(ctx, ld, child, cell);
- cell->s.layout.b = child->s.layout.b;
- }
- }
- /* Advance to next column */
- x += colw[col].actual;
- /* Adjust row height if necessary */
- y = cell->s.layout.b + cell->u.block.padding[B] + cell->u.block.border[B];
- if (y > row->s.layout.b)
- row->s.layout.b = y;
- ++col;
- }
- /* For each cell in the row - adjust final cell heights to fill the row */
- for (cell = row->down; cell; cell = cell->next)
- {
- cell->s.layout.b = row->s.layout.b - (cell->u.block.padding[B] + cell->u.block.border[B]);
- }
- ld->restart = save_restart;
- }
- static void layout_table(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top)
- {
- fz_html_box *row;
- int col, ncol = 0;
- float min_tabw, max_tabw;
- struct column_width *colw;
- fz_html_restarter *restart = ld->restart;
- float spacing;
- int eop = 0;
- float em = box->s.layout.em;
- float *margin = box->u.block.margin;
- float *border = box->u.block.border;
- float *padding = box->u.block.padding;
- float auto_w;
- spacing = fz_from_css_number(box->style->border_spacing, box->s.layout.em, box->s.layout.w, 0);
- if (restart)
- {
- /* We have reached the restart point */
- if (restart->start == box)
- restart->start = NULL;
- }
- /* TODO: remove 'vertical' margin adjustments across automatic page breaks */
- if (restart && restart->start != NULL)
- {
- /* We're still skipping, don't check for pagebreak before! */
- }
- else if (layout_block_page_break(ctx, ld, &top->s.layout.b, box->style->page_break_before))
- eop = 1;
- /* Position table in box flow, and add margins and padding */
- box->s.layout.y = advance_for_spacing(ctx, ld, top->s.layout.b, margin[T] + border[T] + padding[T], &eop);
- if (eop)
- {
- if (restart && restart->end == NULL)
- {
- box->s.layout.b = box->s.layout.y;
- if (restart->potential)
- restart->end = restart->potential;
- else
- restart->end = box;
- return;
- }
- }
- /* Finalize position and width. */
- auto_w = top->s.layout.w - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
- box->s.layout.x = top->s.layout.x + margin[L] + border[L] + padding[L];
- box->s.layout.w = fz_from_css_number(box->style->width, em, auto_w, auto_w);
- /* Add initial border-spacing */
- box->s.layout.b = box->s.layout.y + spacing;
- /* Find the maximum number of columns. (Count 'col' for each row, biggest one gives ncol). */
- for (row = box->down; row; row = row->next)
- {
- fz_html_box *cell;
- col = 0;
- for (cell = row->down; cell; cell = cell->next)
- ++col;
- if (col > ncol)
- ncol = col;
- }
- colw = fz_malloc_array(ctx, ncol, struct column_width);
- // TODO: colgroups and colspan
- fz_try(ctx)
- {
- /* Table Autolayout algorithm from HTML */
- /* https://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.5.2 */
- /* Calculate largest minimum and maximum column widths */
- for (col = 0; col < ncol; ++col)
- {
- colw[col].min = 0;
- colw[col].max = 0;
- }
- for (row = box->down; row; row = row->next)
- {
- fz_html_box *cell, *child;
- for (col = 0, cell = row->down; cell; cell = cell->next, ++col)
- {
- float cell_pad = table_cell_padding(ctx, cell);
- for (child = cell->down; child; child = child->next)
- {
- float min = largest_min_width(ctx, child) + cell_pad;
- float max = largest_max_width(ctx, child) + cell_pad;
- if (min > colw[col].min)
- colw[col].min = min;
- if (max > colw[col].max)
- colw[col].max = max;
- }
- }
- }
- min_tabw = max_tabw = 0;
- for (col = 0; col < ncol; ++col)
- {
- min_tabw += colw[col].min;
- max_tabw += colw[col].max;
- }
- min_tabw += spacing * (ncol + 1);
- max_tabw += spacing * (ncol + 1);
- /* The minimum table width is equal to or wider than the available space.
- * In this case, assign the minimum widths and let the lines overflow...
- */
- if (min_tabw >= box->s.layout.w)
- {
- for (col = 0; col < ncol; ++col)
- colw[col].actual = colw[col].min;
- }
- /* The maximum table width fits within the available space.
- * In this case, set the columns to their maximum widths.
- */
- else if (max_tabw <= box->s.layout.w)
- {
- box->s.layout.w = max_tabw;
- for (col = 0; col < ncol; ++col)
- colw[col].actual = colw[col].max;
- }
- /* The maximum width of the table is greater than the available space, but
- * the minimum table width is smaller. In this case, find the difference
- * between the available space and the minimum table width, lets call it
- * W. Lets also call D the difference between maximum and minimum width of
- * the table.
- *
- * For each column, let d be the difference between maximum and minimum
- * width of that column. Now set the column's width to the minimum width
- * plus d times W over D. This makes columns with large differences
- * between minimum and maximum widths wider than columns with smaller
- * differences.
- */
- else
- {
- float W = (box->s.layout.w - min_tabw);
- float D = (max_tabw - min_tabw);
- for (col = 0; col < ncol; ++col)
- colw[col].actual = colw[col].min + (colw[col].max - colw[col].min) * W / D;
- }
- /* Layout each row in turn. */
- for (row = box->down; row; row = row->next)
- {
- /* Position the row, zero height for now. */
- row->s.layout.x = box->s.layout.x;
- row->s.layout.w = box->s.layout.w;
- row->s.layout.y = row->s.layout.b = box->s.layout.b;
- row->s.layout.b = row->s.layout.y;
- if (restart && restart->start != NULL)
- {
- if (restart->start == row)
- restart->start = NULL;
- else
- {
- squish_block(ctx, row);
- continue; /* still skipping */
- }
- }
- layout_table_row(ctx, ld, row, ncol, colw, spacing);
- /* If the row doesn't fit on the current page, break here and put the row on the next page.
- * Unless the row was at the very start of the page, in which case it'll overflow instead.
- * FIXME: Don't overflow, draw twice with offset to break it abruptly at the page border!
- */
- if (ld->page_h > 0)
- {
- float avail = ld->page_h - fmodf(row->s.layout.y - ld->page_top, ld->page_h);
- float used = row->s.layout.b - row->s.layout.y;
- if (used > avail && avail < ld->page_h)
- {
- if (restart)
- {
- restart->end = row;
- goto exit;
- }
- else
- {
- row->s.layout.y += avail;
- layout_table_row(ctx, ld, row, ncol, colw, spacing);
- }
- }
- }
- box->s.layout.b = row->s.layout.b + spacing;
- }
- exit:;
- }
- fz_always(ctx)
- fz_free(ctx, colw);
- fz_catch(ctx)
- fz_rethrow(ctx);
- if (restart && restart->start != NULL)
- {
- /* We're still skipping, don't check for pagebreak after! */
- }
- else if (layout_block_page_break(ctx, ld, &top->s.layout.b, box->style->page_break_after))
- {
- if (restart && restart->end == NULL)
- {
- if (restart->potential)
- restart->end = restart->potential;
- else
- restart->end = box;
- return;
- }
- }
- }
- /* === LAYOUT BLOCKS === */
- /*
- Layout a BOX_BLOCK.
- ctx: The ctx in use.
- box: The BOX_BLOCK to layout.
- top_x: The x position for left of the topmost box.
- top_b: Pointer to the y position for the top of the topmost box on entry, updated to the y position for the bottom of the topmost box on exit.
- top_w: The width available for the topmost box.
- */
- static void layout_block(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top)
- {
- fz_html_box *child;
- fz_html_restarter *restart = ld->restart;
- const fz_css_style *style = box->style;
- float em = box->s.layout.em;
- float *margin = box->u.block.margin;
- float *border = box->u.block.border;
- float *padding = box->u.block.padding;
- float auto_w;
- int eop = 0;
- assert(fz_html_box_has_boxes(box));
- /* If restart is non-NULL, we are running in restartable mode. */
- if (restart)
- {
- /* If restart->start == box, then we've been skipping, and this
- * is the point at which we should restart. */
- if (restart->start == box)
- {
- /* Set restart->start to be NULL to indicate that we aren't skipping
- * any more. */
- restart->start = NULL;
- }
- /* In the event that no content fits, we want to restart with us, not with the
- * content that doesn't fit. */
- if (restart->potential == NULL)
- restart->potential = box;
- }
- /* TODO: remove 'vertical' margin adjustments across automatic page breaks */
- if (restart && restart->start != NULL)
- {
- /* We're still skipping, don't check for pagebreak before! */
- }
- else if (layout_block_page_break(ctx, ld, &top->s.layout.b, style->page_break_before))
- eop = 1;
- /* Important to remember that box->{x,y,w,b} are the coordinates of the content. The
- * margin/border/paddings are all outside this. */
- box->s.layout.y = top->s.layout.b;
- if (restart && restart->start != NULL)
- {
- /* We're still skipping, so any child should inherit 0 vertical margin from us. */
- }
- else
- {
- /* We're not skipping, so add in the spacings to the top edge of our box. */
- box->s.layout.y = advance_for_spacing(ctx, ld, box->s.layout.y, margin[T] + border[T] + padding[T], &eop);
- }
- if (eop)
- {
- if (restart && restart->end == NULL)
- {
- box->s.layout.b = box->s.layout.y;
- if (restart->potential)
- restart->end = restart->potential;
- else
- restart->end = box;
- return;
- }
- }
- /* Finalize position and width. */
- auto_w = top->s.layout.w - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
- box->s.layout.x = top->s.layout.x + margin[L] + border[L] + padding[L];
- box->s.layout.w = fz_from_css_number(box->style->width, em, auto_w, auto_w);
- /* Start with our content being zero height. */
- box->s.layout.b = box->s.layout.y;
- for (child = box->down; child; child = child->next)
- {
- if (restart && restart->end == NULL)
- {
- /* If advancing over the bottom margin/border/padding of a child has previously
- * brought us to the end of a page, this is where we end. */
- if (eop)
- {
- if (restart->potential)
- restart->end = restart->potential;
- else
- restart->end = child;
- break;
- }
- }
- if (child->type == BOX_BLOCK || child->type == BOX_TABLE)
- {
- assert(fz_html_box_has_boxes(child));
- if (child->type == BOX_BLOCK)
- layout_block(ctx, ld, child, box);
- else
- layout_table(ctx, ld, child, box);
- /* Unless we're still skipping, the base of our box must now be at least as
- * far down as the child, plus the child's spacing. */
- if (!restart || restart->start == NULL)
- {
- box->s.layout.b = advance_for_spacing(ctx, ld,
- child->s.layout.b,
- child->u.block.padding[B] + child->u.block.border[B] + child->u.block.margin[B],
- &eop);
- }
- }
- else if (child->type == BOX_FLOW)
- {
- layout_flow(ctx, ld, child, box);
- if (child->s.layout.b > child->s.layout.y)
- {
- if (!restart || restart->start == NULL)
- {
- box->s.layout.b = child->s.layout.b;
- }
- }
- }
- /* Stop if we've reached the endpoint. */
- if (restart && restart->end != NULL)
- break;
- if (restart && box->s.layout.b != box->s.layout.y)
- restart->potential = NULL;
- }
- /* If we're still skipping, exit with vertical=0. */
- /* If we've reached the endpoint, exit. */
- if (restart && (restart->start != NULL || restart->end != NULL))
- return;
- /* reserve space for the list mark */
- if (box->list_item && box->s.layout.y == box->s.layout.b)
- {
- box->s.layout.b += fz_from_css_number_scale(style->line_height, em);
- }
- if (restart && restart->start != NULL)
- {
- /* We're still skipping, don't check for pagebreak after! */
- }
- else if (layout_block_page_break(ctx, ld, &box->s.layout.b, style->page_break_after))
- {
- if (restart && restart->end == NULL)
- {
- if (restart->potential)
- restart->end = restart->potential;
- else
- restart->end = box;
- return;
- }
- }
- }
- /* === LAYOUT === */
- // Compute new em, padding, border, margin.
- // Also compute layout x and w.
- static void layout_update_styles(fz_context *ctx, fz_html_box *box, fz_html_box *top)
- {
- float top_em = top->s.layout.em;
- float top_baseline = top->s.layout.baseline;
- float top_w = top->s.layout.w;
- while (box)
- {
- const fz_css_style *style = box->style;
- float em = box->s.layout.em = fz_from_css_number(style->font_size, top_em, top_em, top_em);
- if (style->vertical_align == VA_SUPER)
- box->s.layout.baseline = top_baseline - top_em / 3;
- else if (style->vertical_align == VA_SUB)
- box->s.layout.baseline = top_baseline + top_em / 5;
- else
- box->s.layout.baseline = top_baseline;
- if (box->type != BOX_INLINE && box->type != BOX_FLOW)
- {
- float *margin = box->u.block.margin;
- float *border = box->u.block.border;
- float *padding = box->u.block.padding;
- margin[T] = fz_from_css_number(style->margin[T], em, top_w, 0);
- margin[R] = fz_from_css_number(style->margin[R], em, top_w, 0);
- margin[B] = fz_from_css_number(style->margin[B], em, top_w, 0);
- margin[L] = fz_from_css_number(style->margin[L], em, top_w, 0);
- padding[T] = fz_from_css_number(style->padding[T], em, top_w, 0);
- padding[R] = fz_from_css_number(style->padding[R], em, top_w, 0);
- padding[B] = fz_from_css_number(style->padding[B], em, top_w, 0);
- padding[L] = fz_from_css_number(style->padding[L], em, top_w, 0);
- border[T] = style->border_style_0 ? fz_from_css_number(style->border_width[T], em, top_w, 0) : 0;
- border[R] = style->border_style_1 ? fz_from_css_number(style->border_width[R], em, top_w, 0) : 0;
- border[B] = style->border_style_2 ? fz_from_css_number(style->border_width[B], em, top_w, 0) : 0;
- border[L] = style->border_style_3 ? fz_from_css_number(style->border_width[L], em, top_w, 0) : 0;
- // TODO: BLOCK nested inside TABLE!
- if (box->type == BOX_BLOCK || box->type == BOX_TABLE)
- {
- /* Compute preliminary width (will be adjusted if block is nested in table cell later) */
- float auto_w = top_w - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
- box->s.layout.w = fz_from_css_number(box->style->width, em, auto_w, auto_w);
- }
- }
- else if (box->type == BOX_FLOW)
- {
- box->s.layout.x = top->s.layout.x;
- box->s.layout.w = top->s.layout.w;
- }
- if (box->down)
- layout_update_styles(ctx, box->down, box);
- box = box->next;
- }
- }
- static void layout_update_widths(fz_context *ctx, fz_html_box *box, fz_html_box *top, hb_buffer_t *hb_buf)
- {
- while (box)
- {
- if (box->type == BOX_FLOW)
- {
- fz_html_flow *node;
- for (node = box->u.flow.head; node; node = node->next)
- {
- if (node->type == FLOW_IMAGE)
- /* start with "native" size (only used for table width calculations) */
- node->w = node->content.image->w * 72.0f / 96.0f;
- else if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
- measure_string_w(ctx, node, hb_buf);
- }
- }
- if (box->down)
- layout_update_widths(ctx, box->down, box, hb_buf);
- box = box->next;
- }
- }
- static int is_layout_box(fz_html_box *box)
- {
- return box->type == BOX_BLOCK || box->type == BOX_TABLE;
- }
- static int is_empty_block_box(fz_html_box *box)
- {
- fz_html_box *child;
- if (box->type == BOX_BLOCK)
- {
- if (box->u.block.padding[T] != 0 || box->u.block.padding[B] != 0)
- return 0;
- if (box->u.block.border[T] != 0 || box->u.block.border[B] != 0)
- return 0;
- for (child = box->down; child; child = child->next)
- {
- if (child->type != BOX_BLOCK)
- return 0;
- if (!is_empty_block_box(child))
- return 0;
- if (child->u.block.margin[T] != 0 || child->u.block.margin[B] != 0)
- return 0;
- }
- return 1;
- }
- return 0;
- }
- static void layout_collapse_margin_with_children(fz_context *ctx, fz_html_box *here)
- {
- fz_html_box *child, *first, *last = NULL;
- first = here->down;
- for (child = here->down; child; child = child->next)
- {
- layout_collapse_margin_with_children(ctx, child);
- last = child;
- }
- if (is_layout_box(here))
- {
- if (first && is_layout_box(first))
- {
- if (first->u.block.border[T] == 0 && first->u.block.padding[T] == 0)
- {
- float m = fz_max(first->u.block.margin[T], here->u.block.margin[T]);
- here->u.block.margin[T] = m;
- first->u.block.margin[T] = 0;
- }
- }
- if (last && is_layout_box(last))
- {
- if (last->u.block.border[T] == 0 && last->u.block.padding[T] == 0)
- {
- float m = fz_max(last->u.block.margin[B], here->u.block.margin[B]);
- here->u.block.margin[B] = m;
- last->u.block.margin[B] = 0;
- }
- }
- }
- }
- static void layout_collapse_margin_with_siblings(fz_context *ctx, fz_html_box *here)
- {
- while (here)
- {
- fz_html_box *next = here->next;
- if (here->down)
- layout_collapse_margin_with_siblings(ctx, here->down);
- if (is_layout_box(here) && next && is_layout_box(next))
- {
- float m = fz_max(here->u.block.margin[B], next->u.block.margin[T]);
- here->u.block.margin[B] = m;
- next->u.block.margin[T] = 0;
- }
- here = next;
- }
- }
- static void layout_collapse_margin_with_self(fz_context *ctx, fz_html_box *here)
- {
- while (here)
- {
- if (here->down)
- layout_collapse_margin_with_self(ctx, here->down);
- if (is_layout_box(here) && is_empty_block_box(here))
- {
- float m = fz_max(here->u.block.margin[T], here->u.block.margin[B]);
- here->u.block.margin[T] = 0;
- here->u.block.margin[B] = m;
- }
- here = here->next;
- }
- }
- static void layout_collapse_margins(fz_context *ctx, fz_html_box *box, fz_html_box *top)
- {
- // Vertical margins are collapsed in these cases:
- // 1) Adjacent siblings
- // 2) No content separating parent and descendants. The margin ends up outside the parent.
- // 3) Empty blocks
- layout_collapse_margin_with_self(ctx, box);
- layout_collapse_margin_with_children(ctx, box);
- layout_collapse_margin_with_siblings(ctx, box);
- }
- void
- fz_restartable_layout_html(fz_context *ctx, fz_html_tree *tree, float start_x, float start_y, float page_w, float page_h, float em, fz_html_restarter *restart)
- {
- int unlocked = 0;
- layout_data ld = { 0 };
- fz_html_box *box = tree->root;
- fz_var(ld.hb_buf);
- fz_var(unlocked);
- // nothing to layout
- if (!box->down)
- {
- fz_warn(ctx, "html: nothing to layout");
- box->s.layout.em = em;
- box->s.layout.baseline = 0;
- box->s.layout.x = start_x;
- box->s.layout.w = page_w;
- box->s.layout.y = start_y;
- box->s.layout.b = start_y;
- return;
- }
- fz_hb_lock(ctx);
- fz_try(ctx)
- {
- Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
- ld.hb_buf = hb_buffer_create();
- Memento_stopLeaking(); /* HarfBuzz leaks harmlessly */
- unlocked = 1;
- fz_hb_unlock(ctx);
- ld.restart = restart;
- ld.page_h = page_h;
- ld.page_top = start_y;
- ld.pool = tree->pool;
- if (restart)
- restart->potential = NULL;
- // Update em/margin/padding/border if necessary.
- if (box->s.layout.em != em || box->s.layout.x != start_x || box->s.layout.w != page_w)
- {
- box->s.layout.em = em;
- box->s.layout.baseline = 0;
- box->s.layout.x = start_x;
- box->s.layout.w = page_w;
- layout_update_styles(ctx, box->down, box);
- layout_update_widths(ctx, box->down, box, ld.hb_buf);
- layout_collapse_margins(ctx, box->down, box);
- }
- box->s.layout.y = start_y;
- box->s.layout.b = start_y;
- assert(box->type == BOX_BLOCK);
- layout_block(ctx, &ld, box, box); // HACK: layout box with itself as parent!
- }
- fz_always(ctx)
- {
- if (unlocked)
- fz_hb_lock(ctx);
- hb_buffer_destroy(ld.hb_buf);
- fz_hb_unlock(ctx);
- }
- fz_catch(ctx)
- {
- fz_rethrow(ctx);
- }
- }
- void
- fz_layout_html(fz_context *ctx, fz_html *html, float w, float h, float em)
- {
- /* If we're already laid out to the specifications we need,
- * nothing to do. */
- if (html->layout_w == w && html->layout_h == h && html->layout_em == em)
- return;
- html->page_margin[T] = fz_from_css_number(html->tree.root->style->margin[T], em, em, 0);
- html->page_margin[B] = fz_from_css_number(html->tree.root->style->margin[B], em, em, 0);
- html->page_margin[L] = fz_from_css_number(html->tree.root->style->margin[L], em, em, 0);
- html->page_margin[R] = fz_from_css_number(html->tree.root->style->margin[R], em, em, 0);
- html->page_w = w - html->page_margin[L] - html->page_margin[R];
- if (html->page_w <= 72)
- html->page_w = 72; /* enforce a minimum page size! */
- if (h > 0)
- {
- html->page_h = h - html->page_margin[T] - html->page_margin[B];
- if (html->page_h <= 72)
- html->page_h = 72; /* enforce a minimum page size! */
- }
- else
- {
- /* h 0 means no pagination */
- html->page_h = 0;
- }
- fz_restartable_layout_html(ctx, &html->tree, 0, 0, html->page_w, html->page_h, em, NULL);
- if (h == 0)
- html->page_h = html->tree.root->s.layout.b;
- /* Remember how we're laid out so we can avoid needless
- * relayouts in future. */
- html->layout_w = w;
- html->layout_h = h;
- html->layout_em = em;
- #ifndef NDEBUG
- if (fz_atoi(getenv("FZ_DEBUG_HTML")))
- fz_debug_html(ctx, html->tree.root);
- #endif
- }
- /* === DRAW === */
- typedef struct
- {
- float rgb[3];
- float a;
- } unpacked_color;
- static inline unpacked_color
- unpack_color(const fz_css_color src)
- {
- unpacked_color dst;
- dst.rgb[0] = src.r / 255.0f;
- dst.rgb[1] = src.g / 255.0f;
- dst.rgb[2] = src.b / 255.0f;
- dst.a = src.a / 255.0f;
- return dst;
- }
- static inline int
- color_eq(fz_css_color a, fz_css_color b)
- {
- return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
- }
- static int draw_flow_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart)
- {
- fz_html_flow *node;
- fz_text *text = NULL;
- fz_path *line = NULL;
- fz_matrix trm;
- fz_css_color prev_color = { 0, 0, 0, 0 };
- fz_css_color prev_fill_color = { 0, 0, 0, 0 };
- fz_css_color prev_stroke_color = { 0, 0, 0, 0 };
- float line_width, prev_line_width = 0;
- int filling, prev_filling = 0;
- int stroking, prev_stroking = 0;
- int restartable_ended = 0;
- fz_stroke_state *ss = NULL;
- fz_stroke_state *line_ss = NULL;
- fz_var(text);
- fz_var(line);
- fz_var(ss);
- fz_var(line_ss);
- /* FIXME: HB_DIRECTION_TTB? */
- if (restart && restart->start != NULL && restart->start != box)
- return 0;
- fz_try(ctx)
- {
- for (node = box->u.flow.head; node; node = node->next)
- {
- const fz_css_style *style = node->box->style;
- if (restart)
- {
- if (restart->start_flow != NULL)
- {
- if (restart->start_flow != node)
- continue;
- restart->start = NULL;
- restart->start_flow = NULL;
- }
- if (restart->end == box && restart->end_flow == node)
- {
- restartable_ended = 1;
- break;
- }
- }
- if (node->type == FLOW_IMAGE)
- {
- if (node->y >= page_bot || node->y + node->h <= page_top)
- continue;
- }
- else
- {
- if (node->y > page_bot || node->y < page_top)
- continue;
- }
- if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
- {
- string_walker walker;
- const char *s;
- float x, y;
- float em;
- if (node->type == FLOW_SPACE && node->breaks_line)
- continue;
- if (node->type == FLOW_SHYPHEN && !node->breaks_line)
- continue;
- if (style->visibility != V_VISIBLE)
- continue;
- em = node->box->s.layout.em;
- line_width = fz_from_css_number(style->text_stroke_width, em, em, 0);
- filling = style->text_fill_color.a != 0;
- stroking = style->text_stroke_color.a != 0;
- if (stroking)
- {
- if (ss == NULL)
- ss = fz_new_stroke_state(ctx);
- }
- if (line)
- {
- if (line_ss == NULL)
- line_ss = fz_new_stroke_state(ctx);
- }
- if ( /* If we've changed whether we're filling... */
- filling != prev_filling ||
- /* Or we're filling and the color has changed... */
- (prev_filling && !color_eq(style->text_fill_color, prev_fill_color)) ||
- /* Or we've changed whether we're stroking... */
- stroking != prev_stroking ||
- /* Or we're stroking, and the color or linewidth has changed... */
- (prev_stroking && (!color_eq(style->text_stroke_color, prev_stroke_color) || line_width != prev_line_width)))
- {
- if (text)
- {
- if (prev_filling)
- {
- unpacked_color color = unpack_color(prev_fill_color);
- fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
- }
- if (prev_stroking)
- {
- unpacked_color color = unpack_color(prev_stroke_color);
- ss->linewidth = prev_line_width;
- fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
- }
- fz_drop_text(ctx, text);
- text = NULL;
- }
- prev_filling = filling;
- prev_stroking = stroking;
- }
- prev_fill_color = style->text_fill_color;
- prev_stroke_color = style->text_stroke_color;
- prev_line_width = line_width;
- if (!color_eq(style->color, prev_color))
- {
- if (line)
- {
- unpacked_color color = unpack_color(prev_color);
- fz_stroke_path(ctx, dev, line, line_ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
- fz_drop_path(ctx, line);
- line = NULL;
- }
- prev_color = style->color;
- }
- if (style->text_decoration > 0)
- {
- if (!line)
- {
- line = fz_new_path(ctx);
- if (line_ss == NULL)
- line_ss = fz_new_stroke_state(ctx);
- }
- if (style->text_decoration & TD_UNDERLINE)
- {
- fz_moveto(ctx, line, node->x, node->y + 1.5f - page_top);
- fz_lineto(ctx, line, node->x + node->w, node->y + 1.5f - page_top);
- }
- if (style->text_decoration & TD_LINE_THROUGH)
- {
- fz_moveto(ctx, line, node->x, node->y - em * 0.3f - page_top);
- fz_lineto(ctx, line, node->x + node->w, node->y - em * 0.3f - page_top);
- }
- }
- if (!text)
- text = fz_new_text(ctx);
- if (node->bidi_level & 1)
- x = node->x + node->w;
- else
- x = node->x;
- y = node->y;
- trm.a = em;
- trm.b = 0;
- trm.c = 0;
- trm.d = -em;
- trm.e = x;
- trm.f = y - page_top;
- s = get_node_text(ctx, node);
- init_string_walker(ctx, &walker, hb_buf, node->bidi_level & 1, style->font, node->script, node->markup_lang, style->small_caps, s);
- while (walk_string(&walker))
- {
- float node_scale = node->box->s.layout.em / walker.scale;
- unsigned int i;
- uint32_t k;
- int c, n;
- /* Flatten advance and offset into offset array. */
- int x_advance = 0;
- int y_advance = 0;
- for (i = 0; i < walker.glyph_count; ++i)
- {
- walker.glyph_pos[i].x_offset += x_advance;
- walker.glyph_pos[i].y_offset += y_advance;
- x_advance += walker.glyph_pos[i].x_advance;
- y_advance += walker.glyph_pos[i].y_advance;
- }
- if (node->bidi_level & 1)
- x -= x_advance * node_scale;
- /* Walk characters to find glyph clusters */
- k = 0;
- while (walker.start + k < walker.end)
- {
- n = fz_chartorune(&c, walker.start + k);
- for (i = 0; i < walker.glyph_count; ++i)
- {
- if (walker.glyph_info[i].cluster == k)
- {
- trm.e = x + walker.glyph_pos[i].x_offset * node_scale;
- trm.f = y - walker.glyph_pos[i].y_offset * node_scale - page_top;
- fz_show_glyph(ctx, text, walker.font, trm,
- walker.glyph_info[i].codepoint, c,
- 0, node->bidi_level, box->markup_dir, node->markup_lang);
- c = -1; /* for subsequent glyphs in x-to-many mappings */
- }
- }
- /* no glyph found (many-to-many or many-to-one mapping) */
- if (c != -1)
- {
- fz_show_glyph(ctx, text, walker.font, trm,
- -1, c,
- 0, node->bidi_level, box->markup_dir, node->markup_lang);
- }
- k += n;
- }
- if ((node->bidi_level & 1) == 0)
- x += x_advance * node_scale;
- y += y_advance * node_scale;
- }
- }
- else if (node->type == FLOW_IMAGE)
- {
- if (text)
- {
- if (filling)
- {
- unpacked_color color = unpack_color(prev_fill_color);
- fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
- }
- if (stroking)
- {
- unpacked_color color = unpack_color(prev_stroke_color);
- ss->linewidth = line_width;
- fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
- }
- fz_drop_text(ctx, text);
- text = NULL;
- }
- if (style->visibility == V_VISIBLE)
- {
- float alpha = style->color.a / 255.0f;
- fz_matrix itm = fz_pre_translate(ctm, node->x, node->y - page_top);
- itm = fz_pre_scale(itm, node->w, node->h);
- fz_fill_image(ctx, dev, node->content.image, itm, alpha, fz_default_color_params);
- }
- }
- }
- if (text)
- {
- if (filling)
- {
- unpacked_color color = unpack_color(prev_fill_color);
- fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
- }
- if (stroking)
- {
- unpacked_color color = unpack_color(prev_stroke_color);
- ss->linewidth = prev_line_width;
- fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
- }
- fz_drop_text(ctx, text);
- text = NULL;
- }
- if (line)
- {
- unpacked_color color = unpack_color(prev_color);
- fz_stroke_path(ctx, dev, line, line_ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
- fz_drop_path(ctx, line);
- line = NULL;
- }
- }
- fz_always(ctx)
- {
- fz_drop_text(ctx, text);
- fz_drop_path(ctx, line);
- fz_drop_stroke_state(ctx, ss);
- fz_drop_stroke_state(ctx, line_ss);
- }
- fz_catch(ctx)
- fz_rethrow(ctx);
- return restartable_ended;
- }
- static void draw_rect(fz_context *ctx, fz_device *dev, fz_matrix ctm, float page_top, fz_css_color color, float x0, float y0, float x1, float y1)
- {
- if (color.a > 0)
- {
- float rgb[3];
- fz_path *path = fz_new_path(ctx);
- fz_moveto(ctx, path, x0, y0 - page_top);
- fz_lineto(ctx, path, x1, y0 - page_top);
- fz_lineto(ctx, path, x1, y1 - page_top);
- fz_lineto(ctx, path, x0, y1 - page_top);
- fz_closepath(ctx, path);
- rgb[0] = color.r / 255.0f;
- rgb[1] = color.g / 255.0f;
- rgb[2] = color.b / 255.0f;
- fz_fill_path(ctx, dev, path, 0, ctm, fz_device_rgb(ctx), rgb, color.a / 255.0f, fz_default_color_params);
- fz_drop_path(ctx, path);
- }
- }
- static const char *roman_uc[3][10] = {
- { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" },
- { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" },
- { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" },
- };
- static const char *roman_lc[3][10] = {
- { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" },
- { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" },
- { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" },
- };
- static void format_roman_number(fz_context *ctx, char *buf, int size, int n, const char *sym[3][10], const char *sym_m)
- {
- int I = n % 10;
- int X = (n / 10) % 10;
- int C = (n / 100) % 10;
- int M = (n / 1000);
- fz_strlcpy(buf, "", size);
- while (M--)
- fz_strlcat(buf, sym_m, size);
- fz_strlcat(buf, sym[2][C], size);
- fz_strlcat(buf, sym[1][X], size);
- fz_strlcat(buf, sym[0][I], size);
- fz_strlcat(buf, ". ", size);
- }
- static void format_alpha_number(fz_context *ctx, char *buf, int size, int n, int alpha, int omega)
- {
- int base = omega - alpha + 1;
- int tmp[40];
- int i, c;
- if (alpha > 256) /* to skip final-s for greek */
- --base;
- /* Bijective base-26 (base-24 for greek) numeration */
- i = 0;
- while (n > 0)
- {
- --n;
- c = n % base + alpha;
- if (alpha > 256 && c > alpha + 16) /* skip final-s for greek */
- ++c;
- tmp[i++] = c;
- n /= base;
- }
- while (i > 0)
- buf += fz_runetochar(buf, tmp[--i]);
- *buf++ = '.';
- *buf++ = ' ';
- *buf = 0;
- }
- static void format_list_number(fz_context *ctx, int type, int x, char *buf, int size)
- {
- switch (type)
- {
- case LST_NONE: fz_strlcpy(buf, "", size); break;
- case LST_DISC: fz_snprintf(buf, size, "%C ", 0x2022); break; /* U+2022 BULLET */
- case LST_CIRCLE: fz_snprintf(buf, size, "%C ", 0x25CB); break; /* U+25CB WHITE CIRCLE */
- case LST_SQUARE: fz_snprintf(buf, size, "%C ", 0x25A0); break; /* U+25A0 BLACK SQUARE */
- default:
- case LST_DECIMAL: fz_snprintf(buf, size, "%d. ", x); break;
- case LST_DECIMAL_ZERO: fz_snprintf(buf, size, "%02d. ", x); break;
- case LST_LC_ROMAN: format_roman_number(ctx, buf, size, x, roman_lc, "m"); break;
- case LST_UC_ROMAN: format_roman_number(ctx, buf, size, x, roman_uc, "M"); break;
- case LST_LC_ALPHA: format_alpha_number(ctx, buf, size, x, 'a', 'z'); break;
- case LST_UC_ALPHA: format_alpha_number(ctx, buf, size, x, 'A', 'Z'); break;
- case LST_LC_LATIN: format_alpha_number(ctx, buf, size, x, 'a', 'z'); break;
- case LST_UC_LATIN: format_alpha_number(ctx, buf, size, x, 'A', 'Z'); break;
- case LST_LC_GREEK: format_alpha_number(ctx, buf, size, x, 0x03B1, 0x03C9); break;
- case LST_UC_GREEK: format_alpha_number(ctx, buf, size, x, 0x0391, 0x03A9); break;
- }
- }
- static fz_html_flow *find_list_mark_anchor(fz_context *ctx, fz_html_box *box)
- {
- /* find first flow node in <li> tag */
- while (box)
- {
- if (box->type == BOX_FLOW)
- return box->u.flow.head;
- box = box->down;
- }
- return NULL;
- }
- static void draw_list_mark(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, int n)
- {
- fz_font *font;
- fz_text *text;
- fz_matrix trm;
- fz_html_flow *line;
- float y, w;
- float color[4];
- const char *s;
- char buf[40];
- int c, g;
- trm = fz_scale(box->s.layout.em, -box->s.layout.em);
- line = find_list_mark_anchor(ctx, box);
- if (line)
- {
- y = line->y;
- }
- else
- {
- float h = fz_from_css_number_scale(box->style->line_height, box->s.layout.em);
- float a = box->s.layout.em * 0.8f;
- float d = box->s.layout.em * 0.2f;
- if (a + d > h)
- h = a + d;
- y = box->s.layout.y + a + (h - a - d) / 2;
- }
- if (y > page_bot || y < page_top)
- return;
- format_list_number(ctx, box->style->list_style_type, n, buf, sizeof buf);
- s = buf;
- w = 0;
- while (*s)
- {
- s += fz_chartorune(&c, s);
- g = fz_encode_character_with_fallback(ctx, box->style->font, c, UCDN_SCRIPT_LATIN, FZ_LANG_UNSET, &font);
- w += fz_advance_glyph(ctx, font, g, 0) * box->s.layout.em;
- }
- text = fz_new_text(ctx);
- fz_try(ctx)
- {
- s = buf;
- trm.e = box->s.layout.x - w;
- trm.f = y - page_top;
- while (*s)
- {
- s += fz_chartorune(&c, s);
- g = fz_encode_character_with_fallback(ctx, box->style->font, c, UCDN_SCRIPT_LATIN, FZ_LANG_UNSET, &font);
- fz_show_glyph(ctx, text, font, trm, g, c, 0, 0, FZ_BIDI_NEUTRAL, FZ_LANG_UNSET);
- trm.e += fz_advance_glyph(ctx, font, g, 0) * box->s.layout.em;
- }
- color[0] = box->style->color.r / 255.0f;
- color[1] = box->style->color.g / 255.0f;
- color[2] = box->style->color.b / 255.0f;
- color[3] = box->style->color.a / 255.0f;
- fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color, color[3], fz_default_color_params);
- }
- fz_always(ctx)
- fz_drop_text(ctx, text);
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static int draw_block_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart);
- static int draw_table_row(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart);
- static int draw_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart)
- {
- int ret = 0;
- int str = fz_html_tag_to_structure(box->tag);
- if (str != FZ_STRUCTURE_INVALID)
- fz_begin_structure(ctx, dev, str, box->tag, 0);
- switch (box->type)
- {
- case BOX_TABLE_ROW:
- if (restart && restart->end == box)
- ret = 1;
- else if (draw_table_row(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
- ret = 1;
- break;
- case BOX_TABLE:
- case BOX_TABLE_CELL:
- case BOX_BLOCK:
- if (restart && restart->end == box)
- ret = 1;
- else if (draw_block_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
- ret = 1;
- break;
- case BOX_FLOW:
- if (draw_flow_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
- ret = 1;
- break;
- }
- if (str != FZ_STRUCTURE_INVALID)
- fz_end_structure(ctx, dev);
- return ret;
- }
- static void
- do_borders(fz_context *ctx, fz_device *dev, fz_matrix ctm, float page_top, fz_html_box *box, int suppress)
- {
- float *border = box->u.block.border;
- float *padding = box->u.block.padding;
- float x0 = box->s.layout.x - padding[L];
- float y0 = box->s.layout.y - padding[T];
- float x1 = box->s.layout.x + box->s.layout.w + padding[R];
- float y1 = box->s.layout.b + padding[B];
- if (border[T] > 0 && !(suppress & (1<<T)))
- draw_rect(ctx, dev, ctm, page_top, box->style->border_color[T], x0 - border[L], y0 - border[T], x1 + border[R], y0);
- if (border[R] > 0 && !(suppress & (1<<R)))
- draw_rect(ctx, dev, ctm, page_top, box->style->border_color[R], x1, y0 - border[T], x1 + border[R], y1 + border[B]);
- if (border[B] > 0 && !(suppress & (1<<B)))
- draw_rect(ctx, dev, ctm, page_top, box->style->border_color[B], x0 - border[L], y1, x1 + border[R], y1 + border[B]);
- if (border[L] > 0 && !(suppress & (1<<L)))
- draw_rect(ctx, dev, ctm, page_top, box->style->border_color[L], x0 - border[L], y0 - border[T], x0, y1 + border[B]);
- }
- static int draw_block_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart)
- {
- fz_html_box *child;
- float x0, y0, x1, y1;
- float *padding = box->u.block.padding;
- int stopped = 0;
- int skipping;
- assert(fz_html_box_has_boxes(box));
- x0 = box->s.layout.x - padding[L];
- y0 = box->s.layout.y - padding[T];
- x1 = box->s.layout.x + box->s.layout.w + padding[R];
- y1 = box->s.layout.b + padding[B];
- if (y0 > page_bot || y1 < page_top)
- return 0;
- /* If we're skipping, is this the place we should restart? */
- if (restart)
- {
- if (restart->start == box)
- restart->start = NULL;
- if (restart->end == box)
- return 1;
- }
- if (restart && restart->end == box)
- return 1;
- /* Are we skipping? */
- skipping = (restart && restart->start != NULL);
- /* Only draw the content if it's visible */
- if (box->style->visibility == V_VISIBLE)
- {
- int suppress;
- /* We draw the background rectangle regardless if we are skipping or not, because
- * we might find the end-of-skip point inside this box. If there is no content
- * then the box height will be 0, so nothing will be drawn. */
- if (y1 > y0)
- draw_rect(ctx, dev, ctm, page_top, box->style->background_color, x0, y0, x1, y1);
- if (!skipping)
- {
- /* Draw a selection of borders. */
- /* If we are restarting, don't do the bottom one yet. */
- suppress = restart ? (1<<B) : 0;
- do_borders(ctx, dev, ctm, page_top, box, suppress);
- if (box->list_item)
- draw_list_mark(ctx, box, page_top, page_bot, dev, ctm, box->list_item);
- }
- }
- for (child = box->down; child; child = child->next)
- {
- if (draw_box(ctx, child, page_top, page_bot, dev, ctm, hb_buf, restart))
- {
- stopped = 1;
- break;
- }
- }
- if (box->style->visibility == V_VISIBLE && restart && restart->start == NULL)
- {
- /* We didn't draw (at least some of) the borders on the way down,
- * because we were in restart mode. */
- /* We never want to draw the top one. Either it will have been drawn
- * before, or we were skipping at that point. */
- int suppress = (1<<T);
- /* If we were skipping, and we're not any more, better draw in the
- * left and right ones we missed. */
- suppress += (skipping && restart->start == NULL) ? 0 : ((1<<L) + (1<<R));
- /* If we've stopped now, don't do the bottom one either. */
- suppress += stopped ? (1<<B) : 0;
- /* FIXME: background color? list mark is probably OK as we only want
- * it once. */
- do_borders(ctx, dev, ctm, page_top, box, suppress);
- }
- return stopped;
- }
- static int draw_table_row(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart)
- {
- /* Table rows don't draw background colors or borders */
- fz_html_box *child;
- float y0 = box->s.layout.y;
- float y1 = box->s.layout.b;
- if (y0 > page_bot || y1 < page_top)
- return 0;
- /* If we're skipping, is this the place we should restart? */
- if (restart)
- {
- if (restart->start == box)
- restart->start = NULL;
- if (restart->end == box)
- return 1;
- }
- if (restart && restart->end == box)
- return 1;
- for (child = box->down; child; child = child->next)
- if (draw_box(ctx, child, page_top, page_bot, dev, ctm, hb_buf, restart))
- return 1;
- return 0;
- }
- void
- fz_draw_restarted_html(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_html_box *top, float page_top, float page_bot, fz_html_restarter *restart)
- {
- fz_html_box *box;
- hb_buffer_t *hb_buf = NULL;
- int unlocked = 0;
- fz_var(hb_buf);
- fz_var(unlocked);
- fz_hb_lock(ctx);
- fz_try(ctx)
- {
- hb_buf = hb_buffer_create();
- fz_hb_unlock(ctx);
- unlocked = 1;
- for (box = top->down; box; box = box->next)
- if (draw_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
- break;
- }
- fz_always(ctx)
- {
- if (unlocked)
- fz_hb_lock(ctx);
- hb_buffer_destroy(hb_buf);
- fz_hb_unlock(ctx);
- }
- fz_catch(ctx)
- {
- fz_rethrow(ctx);
- }
- }
- void
- fz_draw_html(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_html *html, int page)
- {
- float page_top = page * html->page_h;
- float page_bot = (page + 1) * html->page_h;
- draw_rect(ctx, dev, ctm, 0, html->tree.root->style->background_color,
- 0, 0,
- html->page_w + html->page_margin[L] + html->page_margin[R],
- html->page_h + html->page_margin[T] + html->page_margin[B]);
- ctm = fz_pre_translate(ctm, html->page_margin[L], html->page_margin[T]);
- fz_draw_restarted_html(ctx, dev, ctm, html->tree.root, page_top, page_bot, NULL);
- }
- void fz_draw_story(fz_context *ctx, fz_story *story, fz_device *dev, fz_matrix ctm)
- {
- float page_top, page_bot;
- fz_html_box *b;
- fz_path *clip;
- fz_rect bbox;
- if (story == NULL || story->complete)
- return;
- bbox = story->bbox;
- b = story->tree.root;
- page_top = b->s.layout.y - b->u.block.margin[T] - b->u.block.border[T] - b->u.block.padding[T];
- page_bot = b->s.layout.b + b->u.block.margin[B] + b->u.block.border[B] + b->u.block.padding[B];
- if (dev)
- {
- clip = fz_new_path(ctx);
- fz_try(ctx)
- {
- fz_moveto(ctx, clip, bbox.x0, bbox.y0);
- fz_lineto(ctx, clip, bbox.x1, bbox.y0);
- fz_lineto(ctx, clip, bbox.x1, bbox.y1);
- fz_lineto(ctx, clip, bbox.x0, bbox.y1);
- fz_closepath(ctx, clip);
- fz_clip_path(ctx, dev, clip, 0, ctm, bbox);
- }
- fz_always(ctx)
- fz_drop_path(ctx, clip);
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- story->restart_place = story->restart_draw;
- if (dev)
- fz_draw_restarted_html(ctx, dev, ctm, story->tree.root->down, 0, page_bot+page_top, &story->restart_place);
- story->restart_place.start = story->restart_draw.end;
- story->restart_place.start_flow = story->restart_draw.end_flow;
- story->restart_place.end = NULL;
- story->restart_place.end_flow = NULL;
- story->rect_count++;
- if (story->restart_place.start == NULL)
- story->complete = 1;
- if (dev)
- fz_pop_clip(ctx, dev);
- }
- void fz_reset_story(fz_context *ctx, fz_story *story)
- {
- if (story == NULL)
- return;
- story->restart_place.start = NULL;
- story->restart_place.start_flow = NULL;
- story->restart_place.end = NULL;
- story->restart_place.end_flow = NULL;
- story->restart_draw.start = NULL;
- story->restart_draw.start_flow = NULL;
- story->restart_draw.end = NULL;
- story->restart_draw.end_flow = NULL;
- story->rect_count = 0;
- story->complete = 0;
- }
- static char *
- gather_text(fz_context *ctx, fz_html_box *box)
- {
- fz_html_flow *node;
- char *text = NULL;
- fz_var(text);
- fz_try(ctx)
- {
- for (node = box->u.flow.head; node; node = node->next)
- {
- const fz_css_style *style = node->box->style;
- if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
- {
- const char *s;
- if (node->type == FLOW_SPACE && node->breaks_line)
- continue;
- if (node->type == FLOW_SHYPHEN && !node->breaks_line)
- continue;
- if (style->visibility != V_VISIBLE)
- continue;
- s = get_node_text(ctx, node);
- if (text)
- {
- size_t newsize = strlen(text) + strlen(s) + 1;
- text = fz_realloc(ctx, text, newsize);
- strcat(text, s);
- }
- else
- {
- text = fz_strdup(ctx, s);
- }
- }
- else if (node->type == FLOW_IMAGE)
- {
- }
- }
- }
- fz_catch(ctx)
- {
- fz_free(ctx, text);
- fz_rethrow(ctx);
- }
- return text;
- }
- static int enumerate_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_story_position_callback *cb, void *arg, int depth, int rect_num, fz_html_restarter *restart);
- static int enumerate_block_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_story_position_callback *cb, void *arg, int depth, int rect_num, fz_html_restarter *restart)
- {
- fz_html_box *child;
- float y0, y1;
- float *padding = box->u.block.padding;
- int stopped = 0;
- int skipping;
- fz_story_element_position pos;
- int heading;
- assert(fz_html_box_has_boxes(box));
- y0 = box->s.layout.y - padding[T];
- y1 = box->s.layout.b + padding[B];
- if (y0 > page_bot || y1 < page_top)
- return 0;
- /* If we're skipping, is this the place we should restart? */
- if (restart)
- {
- if (restart->start == box)
- restart->start = NULL;
- if (restart->end == box)
- return 1;
- }
- if (restart && restart->end == box)
- return 1;
- /* Are we skipping? */
- skipping = (restart && restart->start != NULL);
- if (box->style->visibility == V_VISIBLE && !skipping)
- {
- heading = box->heading;
- if (heading || box->id != NULL || box->href)
- {
- /* We have a box worthy of a callback. */
- char *text = NULL;
- pos.text = NULL;
- if (heading && box->down)
- pos.text = text = gather_text(ctx, box->down);
- pos.depth = depth;
- pos.heading = heading;
- pos.open_close = 1;
- pos.id = box->id;
- pos.href = box->href;
- pos.rect.x0 = box->s.layout.x;
- pos.rect.y0 = box->s.layout.y;
- pos.rect.x1 = box->s.layout.x + box->s.layout.w;
- pos.rect.y1 = box->s.layout.b;
- pos.rectangle_num = rect_num;
- fz_try(ctx)
- cb(ctx, arg, &pos);
- fz_always(ctx)
- fz_free(ctx, text);
- fz_catch(ctx)
- fz_rethrow(ctx);
- pos.text = NULL;
- }
- }
- for (child = box->down; child; child = child->next)
- {
- if (enumerate_box(ctx, child, page_top, page_bot, cb, arg, depth+1, rect_num, restart))
- {
- stopped = 1;
- break;
- }
- }
- if (box->style->visibility == V_VISIBLE && !skipping)
- {
- if (heading || box->id != NULL || box->href)
- {
- /* We have a box worthy of a callback that needs closing. */
- pos.open_close = 2;
- pos.rectangle_num = rect_num;
- cb(ctx, arg, &pos);
- }
- }
- return stopped;
- }
- static int enumerate_flow_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_story_position_callback *cb, void *arg, int depth, int rect_num, fz_html_restarter *restart)
- {
- fz_html_flow *node;
- int restartable_ended = 0;
- /* FIXME: HB_DIRECTION_TTB? */
- if (restart && restart->start != NULL && restart->start != box)
- return 0;
- for (node = box->u.flow.head; node; node = node->next)
- {
- const fz_css_style *style = node->box->style;
- if (restart)
- {
- if (restart->start_flow != NULL)
- {
- if (restart->start_flow != node)
- continue;
- restart->start = NULL;
- restart->start_flow = NULL;
- }
- if (restart->end == box && restart->end_flow == node)
- {
- restartable_ended = 1;
- break;
- }
- }
- if (node->type == FLOW_IMAGE)
- {
- if (node->y >= page_bot || node->y + node->h <= page_top)
- continue;
- }
- else
- {
- if (node->y > page_bot || node->y < page_top)
- continue;
- }
- if (node->box->id || node->box->href)
- {
- /* We have a node to callback for. */
- fz_story_element_position pos;
- pos.text = NULL;
- pos.depth = depth;
- pos.heading = 0;
- pos.open_close = 1 | 2;
- pos.id = node->box->id;
- pos.href = node->box->href;
- /* We only have the baseline and the em, so the bbox is a bit of a fudge. */
- pos.rect.x0 = node->x;
- pos.rect.y0 = node->y - node->h * 0.8f;
- pos.rect.x1 = node->x + node->w;
- pos.rect.y1 = node->y + node->h * 0.2f;
- pos.rectangle_num = rect_num;
- cb(ctx, arg, &pos);
- }
- if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
- {
- }
- else if (node->type == FLOW_IMAGE)
- {
- if (style->visibility == V_VISIBLE)
- {
- /* FIXME: Maybe callback for images? */
- }
- }
- }
- return restartable_ended;
- }
- static int enumerate_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_story_position_callback *cb, void *arg, int depth, int rect_num, fz_html_restarter *restart)
- {
- switch (box->type)
- {
- case BOX_TABLE:
- case BOX_TABLE_ROW:
- case BOX_TABLE_CELL:
- case BOX_BLOCK:
- if (restart && restart->end == box)
- return 1;
- if (enumerate_block_box(ctx, box, page_top, page_bot, cb, arg, depth, rect_num, restart))
- return 1;
- break;
- case BOX_FLOW:
- if (enumerate_flow_box(ctx, box, page_top, page_bot, cb, arg, depth, rect_num, restart))
- return 1;
- break;
- }
- return 0;
- }
- void fz_story_positions(fz_context *ctx, fz_story *story, fz_story_position_callback *cb, void *arg)
- {
- float page_top, page_bot;
- fz_html_box *b;
- fz_html_restarter restart;
- fz_html_box *box;
- fz_html_box *top;
- if (story == NULL || story->complete)
- return;
- b = story->tree.root;
- page_top = b->s.layout.y - b->u.block.margin[T] - b->u.block.border[T] - b->u.block.padding[T];
- page_bot = b->s.layout.b + b->u.block.margin[B] + b->u.block.border[B] + b->u.block.padding[B];
- top = story->tree.root->down;
- restart = story->restart_draw;
- for (box = top->down; box; box = box->next)
- if (enumerate_box(ctx, box, page_top, page_bot, cb, arg, 0, story->rect_count+1, &restart))
- break;
- }
|