void EpubFormatter::HandleTagPagebreak(HtmlToken *t)
{
    AttrInfo *attr = t->GetAttrByName("page_path");
    if (!attr || pagePath)
        ForceNewPage();
    if (attr) {
        RectF bbox(0, currY, pageDx, 0);
        currPage->instructions.Append(DrawInstr::Anchor(attr->val, attr->valLen, bbox));
        pagePath.Set(str::DupN(attr->val, attr->valLen));
        // reset CSS style rules for the new document
        styleRules.Reset();
    }
}
// the name doesn't quite fit: this handles FB2 tags
void Fb2Formatter::HandleHtmlTag(HtmlToken *t)
{
    if (Tag_Title == t->tag || Tag_Subtitle == t->tag) {
        bool isSubtitle = Tag_Subtitle == t->tag;
        ScopedMem<char> name(str::Format("h%d", section + (isSubtitle ? 1 : 0)));
        HtmlToken tok;
        tok.SetTag(t->type, name, name + str::Len(name));
        HandleTagHx(&tok);
        HandleAnchorAttr(t);
        if (!isSubtitle && t->IsStartTag()) {
            char *link = (char *)Allocator::Alloc(textAllocator, 24);
            sprintf_s(link, 24, FB2_TOC_ENTRY_MARK "%d", ++titleCount);
            currPage->instructions.Append(DrawInstr::Anchor(link, str::Len(link), RectF(0, currY, pageDx, 0)));
        }
    }
    else if (Tag_Section == t->tag) {
        if (t->IsStartTag())
            section++;
        else if (t->IsEndTag() && section > 1)
            section--;
        FlushCurrLine(true);
        HandleAnchorAttr(t);
    }
    else if (Tag_P == t->tag) {
        if (!tagNesting.Contains(Tag_Title))
            HtmlFormatter::HandleHtmlTag(t);
    }
    else if (Tag_Image == t->tag) {
        HandleTagImg(t);
        HandleAnchorAttr(t);
    }
    else if (Tag_A == t->tag) {
        HandleTagA(t, "href", "http://www.w3.org/1999/xlink");
        HandleAnchorAttr(t, true);
    }
    else if (Tag_Pagebreak == t->tag)
        ForceNewPage();
    else if (Tag_Strong == t->tag)
        HandleTagAsHtml(t, "b");
    else if (t->NameIs("emphasis"))
        HandleTagAsHtml(t, "i");
    else if (t->NameIs("epigraph"))
        HandleTagAsHtml(t, "blockquote");
    else if (t->NameIs("empty-line")) {
        if (!t->IsEndTag())
            EmitParagraph(0);
    }
    else if (t->NameIs("stylesheet"))
        HandleTagAsHtml(t, "style");
}
bool HtmlFormatter::EmitImage(ImageData* img) {
    CrashIf(!img->data);
    Size imgSize = BitmapSizeFromData(img->data, img->len);
    if (imgSize.Empty())
        return false;

    SizeF newSize((REAL)imgSize.Width, (REAL)imgSize.Height);
    // move overly large images to a new line (if they don't fit entirely)
    if (!IsCurrLineEmpty() && (currX + newSize.Width > pageDx || currY + newSize.Height > pageDy))
        FlushCurrLine(false);
    // move overly large images to a new page
    // (if they don't fit even when scaled down to 75%)
    REAL scalePage = std::min((pageDx - currX) / newSize.Width, pageDy / newSize.Height);
    if (currY > 0 && currY + newSize.Height * std::min(scalePage, 0.75f) > pageDy)
        ForceNewPage();
    // if image is bigger than the available space, scale it down
    if (newSize.Width > pageDx - currX || newSize.Height > pageDy - currY) {
        REAL scale = std::min(scalePage, (pageDy - currY) / newSize.Height);
        // scale down images that follow right after a line
        // containing a single image as little as possible,
        // as they might be intended to be of the same size
        if (scale < scalePage && HasPreviousLineSingleImage(currPage->instructions)) {
            ForceNewPage();
            scale = scalePage;
        }
        if (scale < 1) {
            newSize.Width = std::min(newSize.Width * scale, pageDx - currX);
            newSize.Height = std::min(newSize.Height * scale, pageDy - currY);
        }
    }

    RectF bbox(PointF(currX, 0), newSize);
    AppendInstr(DrawInstr::Image(img->data, img->len, bbox));
    currX += bbox.Width;

    return true;
}
void HtmlFormatter::EmitEmptyLine(float lineDy) {
    CrashIf(!IsCurrLineEmpty());
    currY += lineDy;
    if (currY <= pageDy) {
        currX = NewLineX();
        // remove all spaces (only keep SetFont, LinkStart and Anchor instructions)
        for (size_t k = currLineInstr.size(); k > 0; k--) {
            DrawInstr& i = currLineInstr.at(k - 1);
            if (InstrFixedSpace == i.type || InstrElasticSpace == i.type)
                currLineInstr.RemoveAt(k - 1);
        }
        return;
    }
    ForceNewPage();
}
MobiFormatter::MobiFormatter(HtmlFormatterArgs* args, MobiDoc *doc) :
    HtmlFormatter(args), doc(doc)
{
    bool fromBeginning = (0 == args->reparseIdx);
    if (!doc || !fromBeginning)
        return;

    ImageData *img = doc->GetCoverImage();
    if (!img)
        return;

    // TODO: vertically center the cover image?
    EmitImage(img);
    // only add a new page if the image isn't broken
    if (currLineInstr.Count() > 0)
        ForceNewPage();
}
Fb2Formatter::Fb2Formatter(HtmlFormatterArgs *args, Fb2Doc *doc) :
    HtmlFormatter(args), fb2Doc(doc), section(1), titleCount(0)
{
    if (args->reparseIdx != 0)
        return;
    ImageData *cover = doc->GetCoverImage();
    if (!cover)
        return;
    EmitImage(cover);
    // render larger images alone on the cover page,
    // smaller images just separated by a horizontal line
    if (0 == currLineInstr.Count())
        /* the image was broken */;
    else if (currLineInstr.Last().bbox.Height > args->pageDy / 2)
        ForceNewPage();
    else
        EmitHr();
}
void MobiFormatter::HandleHtmlTag(HtmlToken *t)
{
    CrashIf(!t->IsTag());

    if (Tag_P == t->tag || Tag_Blockquote == t->tag) {
        HtmlFormatter::HandleHtmlTag(t);
        HandleSpacing_Mobi(t);
    } else if (Tag_Mbp_Pagebreak == t->tag) {
        ForceNewPage();
    } else if (Tag_A == t->tag) {
        HandleAnchorAttr(t);
        // handle internal and external links (prefer internal ones)
        if (!HandleTagA(t, "filepos"))
            HandleTagA(t);
    } else if (Tag_Hr == t->tag) {
        // imitating Kindle: hr is proceeded by an empty line
        FlushCurrLine(false);
        EmitEmptyLine(lineSpacing);
        EmitHr();
    } else {
        HtmlFormatter::HandleHtmlTag(t);
    }
}