point TextBox::reflowText(LayoutEngine& eng, const Dimensions& containing, const point& current_cursor) { // clear old data so we can re-calculate it. // XXX Future note. // unless the text has changed or the width of the containing box changes we shouldn't // have to recalculate this. lines_.clear(); point cursor = current_cursor; FixedPoint y1 = cursor.y + getOffset().y; // XXX if padding left/border left applies should reduce width and move cursor position if isFirstInlineChild() is set. // Simlarly the last line width should be reduced by padding right/border right. FixedPoint width = eng.getWidthAtPosition(y1, y1 + getLineHeight(), containing.content_.width) - cursor.x + eng.getXAtPosition(y1, y1 + getLineHeight()); Text::iterator last_it = txt_->begin(); Text::iterator it = last_it; bool done = false; while(it != txt_->end()) { LinePtr line = txt_->reflowText(it, width, getStyleNode()); if(line != nullptr && !line->line.empty()) { // is the line larger than available space and are there floats present? FixedPoint last_x = line->line.back().advance.back().x; if(last_x > width && eng.hasFloatsAtPosition(y1, y1 + getLineHeight())) { cursor.y += getLineHeight(); y1 = cursor.y + getOffset().y; cursor.x = eng.getXAtPosition(y1, y1 + getLineHeight()); it = last_it; width = eng.getWidthAtPosition(y1, y1 + getLineHeight(), containing.content_.width); continue; } lines_.emplace_back(line, cursor); lines_.back().width_ = calculateWidth(lines_.back()); // XXX This height needs to be modified later if we have inline elements with a different lineheight lines_.back().height_ = getLineHeight(); cursor.x += lines_.back().width_; if(line->is_end_line) { // update the cursor for the next line cursor.y += getLineHeight(); y1 = cursor.y + getOffset().y; cursor.x = eng.getXAtPosition(y1, y1 + getLineHeight()); } } } int max_w = 0; for(auto& line : lines_) { max_w = std::max(max_w, line.width_); } setContentWidth(max_w); if(!lines_.empty()) { setContentHeight(lines_.back().offset_.y + getLineHeight()); } return cursor; }
int main(int argc, char *argv[]) { le_int32 failures = 0; for (le_int32 test = 0; test < testCount; test += 1) { LEErrorCode fontStatus = LE_NO_ERROR; printf("Test %d, font = %s... ", test, testInputs[test].fontName); PortableFontInstance fontInstance(testInputs[test].fontName, 12, fontStatus); if (LE_FAILURE(fontStatus)) { printf("could not open font.\n"); continue; } LEErrorCode success = LE_NO_ERROR; LayoutEngine *engine = LayoutEngine::layoutEngineFactory(&fontInstance, testInputs[test].scriptCode, -1, success); le_int32 textLength = testInputs[test].textLength; le_bool result; TestResult actual; if (LE_FAILURE(success)) { // would be nice to print the script name here, but // don't want to maintain a table, and don't want to // require ICU just for the script name... printf("could not create a LayoutEngine.\n"); continue; } actual.glyphCount = engine->layoutChars(testInputs[test].text, 0, textLength, textLength, testInputs[test].rightToLeft, 0, 0, success); actual.glyphs = new LEGlyphID[actual.glyphCount]; actual.indices = new le_int32[actual.glyphCount]; actual.positions = new float[actual.glyphCount * 2 + 2]; engine->getGlyphs(actual.glyphs, success); engine->getCharIndices(actual.indices, success); engine->getGlyphPositions(actual.positions, success); result = compareResults(test, &testResults[test], &actual); if (result) { printf("passed.\n"); } else { failures += 1; printf("failed.\n"); } delete[] actual.positions; delete[] actual.indices; delete[] actual.glyphs; delete engine; } return failures; }
void TextBox::handleLayout(LayoutEngine& eng, const Dimensions& containing) { // TextBox's have no children to deal with, by definition. // XXX fix the point() to be the actual last point, say from LayoutEngine point cursor = reflowText(eng, containing, eng.getCursor()); eng.setCursor(cursor); calculateHorzMPB(containing.content_.width); calculateVertMPB(containing.content_.height); }
/* * Class: sun_font_SunLayoutEngine * Method: nativeLayout * Signature: (Lsun/font/FontStrike;[CIIIIZLjava/awt/geom/Point2D$Float;Lsun/font/GlyphLayout$GVData;)V */ JNIEXPORT void JNICALL Java_sun_font_SunLayoutEngine_nativeLayout (JNIEnv *env, jclass cls, jobject font2d, jobject strike, jfloatArray matrix, jint gmask, jint baseIndex, jcharArray text, jint start, jint limit, jint min, jint max, jint script, jint lang, jint typo_flags, jobject pt, jobject gvdata, jlong upem, jlong layoutTables) { // fprintf(stderr, "nl font: %x strike: %x script: %d\n", font2d, strike, script); fflush(stderr); float mat[4]; env->GetFloatArrayRegion(matrix, 0, 4, mat); FontInstanceAdapter fia(env, font2d, strike, mat, 72, 72, (le_int32) upem, (TTLayoutTableCache *) layoutTables); LEErrorCode success = LE_NO_ERROR; LayoutEngine *engine = LayoutEngine::layoutEngineFactory(&fia, script, lang, typo_flags & TYPO_MASK, success); if (min < 0) min = 0; if (max < min) max = min; /* defensive coding */ // have to copy, yuck, since code does upcalls now. this will be soooo slow jint len = max - min; jchar buffer[256]; jchar* chars = buffer; if (len > 256) { size_t size = len * sizeof(jchar); if (size / sizeof(jchar) != len) { return; } chars = (jchar*)malloc(size); if (chars == 0) { return; } } // fprintf(stderr, "nl chars: %x text: %x min %d len %d typo %x\n", chars, text, min, len, typo_flags); fflush(stderr); env->GetCharArrayRegion(text, min, len, chars); jfloat x, y; getFloat(env, pt, x, y); jboolean rtl = (typo_flags & TYPO_RTL) != 0; int glyphCount = engine->layoutChars(chars, start - min, limit - start, len, rtl, x, y, success); // fprintf(stderr, "sle nl len %d -> gc: %d\n", len, glyphCount); fflush(stderr); engine->getGlyphPosition(glyphCount, x, y, success); // fprintf(stderr, "layout glyphs: %d x: %g y: %g\n", glyphCount, x, y); fflush(stderr); if (putGV(env, gmask, baseIndex, gvdata, engine, glyphCount)) { // !!! hmmm, could use current value in positions array of GVData... putFloat(env, pt, x, y); } if (chars != buffer) { free(chars); } delete engine; }
void InlineBlockBox::handlePreChildLayout2(LayoutEngine& eng, const Dimensions& containing) { cursor_ = eng.getCursor(); eng.setCursor(point(0, 0)); if(!getChildren().empty() || !isReplaceable()) { setContentHeight(0); } else if(isReplaceable()) { NodePtr node = getNode(); const rect& r = node->getDimensions(); setContentWidth(r.w() * LayoutEngine::getFixedPointScale()); setContentHeight(r.h() * LayoutEngine::getFixedPointScale()); } }
RootBoxPtr Box::createLayout(StyleNodePtr node, int containing_width, int containing_height) { LayoutEngine e; // search for the body element then render that content. node->preOrderTraversal([&e, containing_width, containing_height](StyleNodePtr node){ auto n = node->getNode(); if(n->id() == NodeId::ELEMENT && n->hasTag(ElementId::HTML)) { e.layoutRoot(node, nullptr, point(containing_width * LayoutEngine::getFixedPointScale(), containing_height * LayoutEngine::getFixedPointScale())); return false; } return true; }); node->getNode()->layoutComplete(); auto root_box = e.getRoot(); root_box->setLayoutDimensions(containing_width, containing_height); return root_box; }
void BlockBox::handleLayout(LayoutEngine& eng, const Dimensions& containing) { if(isReplaceable()) { layoutChildren(eng); } else { layoutChildren(eng); layoutHeight(containing); } if(isFloat()) { FixedPoint top = 0; const FixedPoint lh = getStyleNode()->getHeight()->isAuto() ? getLineHeight() : getStyleNode()->getHeight()->getLength().compute(containing.content_.height); const FixedPoint box_w = getDimensions().content_.width; FixedPoint y = 0; FixedPoint x = 0; FixedPoint y1 = y + getOffset().y; FixedPoint left = getStyleNode()->getFloat() == Float::LEFT ? eng.getXAtPosition(y1, y1 + lh) + x : eng.getX2AtPosition(y1, y1 + lh); FixedPoint w = eng.getWidthAtPosition(y1, y1 + lh, containing.content_.width); bool placed = false; while(!placed) { if(w >= box_w) { left = left - (getStyleNode()->getFloat() == Float::LEFT ? x : box_w) + getMBPLeft(); top = y + getMBPTop() + containing.content_.height; placed = true; } else { y += lh; y1 = y + getOffset().y; left = getStyleNode()->getFloat() == Float::LEFT ? eng.getXAtPosition(y1, y1 + lh) + x : eng.getX2AtPosition(y1, y1 + lh); w = eng.getWidthAtPosition(y1, y1 + lh, containing.content_.width); } } setContentX(left); setContentY(top); } else { for(auto& child : getChildren()) { setContentHeight(child->getTop() + child->getHeight() + child->getMBPBottom()); } } }
void Box::layout(LayoutEngine& eng, const Dimensions& ocontaining) { auto containing = ocontaining; auto styles = getStyleNode(); std::unique_ptr<LayoutEngine::FloatContextManager> fcm; if(getParent() && getParent()->isFloat()) { fcm.reset(new LayoutEngine::FloatContextManager(eng, FloatList())); } point cursor; // If we have a clear flag set, then move the cursor in the layout engine to clear appropriate floats. if(node_ != nullptr) { eng.moveCursorToClearFloats(node_->getClear(), cursor); } NodePtr node = getNode(); std::unique_ptr<RenderContext::Manager> ctx_manager; if(node != nullptr) { ctx_manager.reset(new RenderContext::Manager(node->getProperties())); } if(styles != nullptr) { auto ovf = styles->getOverflow(); // this is kind of a hack, since we really want to avoid re-running layout //if(ovf == Overflow::SCROLL || ovf == Overflow::AUTO) { containing.content_.width -= scrollbar_default_width * LayoutEngine::getFixedPointScale(); //} } handlePreChildLayout(eng, containing); if(node_ != nullptr) { const std::vector<StyleNodePtr>& node_children = node_->getChildren(); if(!node_children.empty()) { boxes_ = eng.layoutChildren(node_children, shared_from_this()); } } for(auto& child : boxes_) { if(child->isFloat()) { handlePreChildLayout3(eng, containing); child->layout(eng, dimensions_); handlePostFloatChildLayout(eng, child); eng.addFloat(child); } } offset_ = (getParent() != nullptr ? getParent()->getOffset() : point()) + point(dimensions_.content_.x, dimensions_.content_.y); if(isBlockBox()) { const FixedPoint y1 = offset_.y; point p(eng.getXAtPosition(y1, y1 + getLineHeight()), 0); eng.setCursor(p); } handlePreChildLayout2(eng, containing); for(auto& child : boxes_) { if(!child->isFloat()) { handlePreChildLayout3(eng, containing); child->layout(eng, dimensions_); handlePostChildLayout(eng, child); } } handleLayout(eng, containing); //layoutAbsolute(eng, containing); for(auto& child : boxes_) { child->postParentLayout(eng, dimensions_); } // need to call this after doing layout, since we need to now what the computed padding/border values are. border_info_.init(dimensions_); background_info_.init(dimensions_); if(isBlockBox() && !isFloat()) { point p; p.y = getTop() + getHeight() + getMBPBottom(); p.x = eng.getXAtPosition(p.y, p.y + getLineHeight()); eng.setCursor(p); } precss_content_height_ = dimensions_.content_.height; if(isBlockBox() && styles != nullptr) { auto css_h = styles->getHeight(); if(!css_h->isAuto()) { setContentHeight(css_h->getLength().compute(containing.content_.height)); } } eng.closeLineBox(); }
void InlineBlockBox::handleLayout(LayoutEngine& eng, const Dimensions& containing) { eng.setCursor(cursor_); layoutChildren(eng); layoutHeight(containing); if(isReplaceable()) { NodePtr node = getNode(); node->setDimensions(rect(0, 0, getWidth() / LayoutEngine::getFixedPointScale(), getHeight() / LayoutEngine::getFixedPointScale())); } // try and fit the box at cursor, failing that we move the cursor and try again. FixedPoint width_at_cursor = eng.getWidthAtPosition(eng.getCursor().y, eng.getCursor().y + getHeight() + getMBPHeight(), containing.content_.width) - eng.getCursor().x + eng.getXAtPosition(eng.getCursor().y, eng.getCursor().y + getHeight() + getMBPHeight()); if(getWidth() + getMBPWidth() > width_at_cursor) { point p = eng.getCursor(); p.y += getLineHeight(); while(eng.hasFloatsAtPosition(p.y, p.y + getHeight() + getMBPHeight()) && getWidth() + getMBPWidth() > width_at_cursor) { width_at_cursor = eng.getWidthAtPosition(p.y, p.y + getHeight() + getMBPHeight(), containing.content_.width); } p.x = eng.getXAtPosition(p.y, p.y + getHeight() + getMBPHeight()); setContentX(p.x); setContentY(p.y); p.y += getHeight() + getMBPHeight(); p.x = eng.getXAtPosition(p.y, p.y + getLineHeight()); eng.setCursor(p); } else { setContentX(eng.getCursor().x); // XXX if height is greater than other objects on line we need to increase lineheight. setContentY(eng.getCursor().y); eng.setCursor(point(getLeft() + getWidth() + getMBPRight(), eng.getCursor().y)); } }
void ListItemBox::handleLayout(LayoutEngine& eng, const Dimensions& containing) { auto lst = getStyleNode()->getListStyleType(); switch(lst) { case ListStyleType::DISC: /* is the default */ break; case ListStyleType::CIRCLE: marker_ = utils::codepoint_to_utf8(marker_circle); break; case ListStyleType::SQUARE: marker_ = utils::codepoint_to_utf8(marker_square); break; case ListStyleType::DECIMAL: { std::ostringstream ss; ss << std::dec << count_ << "."; marker_ = ss.str(); break; } case ListStyleType::DECIMAL_LEADING_ZERO: { std::ostringstream ss; ss << std::dec << std::setfill('0') << std::setw(2) << count_ << "."; marker_ = ss.str(); break; } case ListStyleType::LOWER_ROMAN: if(count_ < 4000) { marker_ = to_roman(count_, true) + "."; } break; case ListStyleType::UPPER_ROMAN: if(count_ < 4000) { marker_ = to_roman(count_, false) + "."; } break; case ListStyleType::LOWER_GREEK: if(count_ <= (marker_lower_greek_end - marker_lower_greek + 1)) { marker_ = utils::codepoint_to_utf8(marker_lower_greek + count_) + "."; } break; case ListStyleType::LOWER_ALPHA: case ListStyleType::LOWER_LATIN: if(count_ <= (marker_lower_latin_end - marker_lower_latin + 1)) { marker_ = utils::codepoint_to_utf8(marker_lower_latin + count_) + "."; } break; case ListStyleType::UPPER_ALPHA: case ListStyleType::UPPER_LATIN: if(count_ <= (marker_upper_latin_end - marker_upper_latin + 1)) { marker_ = utils::codepoint_to_utf8(marker_upper_latin + count_) + "."; } break; case ListStyleType::ARMENIAN: if(count_ <= (marker_armenian_end - marker_armenian + 1)) { marker_ = utils::codepoint_to_utf8(marker_armenian + count_) + "."; } break; case ListStyleType::GEORGIAN: if(count_ <= (marker_georgian_end - marker_georgian + 1)) { marker_ = utils::codepoint_to_utf8(marker_georgian + count_) + "."; } break; case ListStyleType::NONE: default: marker_.clear(); break; } FixedPoint top = getMBPTop() + containing.content_.height; FixedPoint left = getMBPLeft(); if(isFloat()) { // XXX fixme to use a more intelligent approach than iterating every pixel! const FixedPoint lh = 65536;//getDimensions().content_.height; const FixedPoint box_w = getDimensions().content_.width; FixedPoint y = getMBPTop(); FixedPoint x = getMBPLeft(); FixedPoint y1 = y + getOffset().y; left = getStyleNode()->getFloat() == Float::LEFT ? eng.getXAtPosition(y1, y1 + lh) + x : eng.getX2AtPosition(y1, y1 + lh); FixedPoint w = eng.getWidthAtPosition(y1, y1 + lh, containing.content_.width); bool placed = false; while(!placed) { if(w >= box_w) { left = left - (getStyleNode()->getFloat() == Float::LEFT ? x : box_w); top = y; placed = true; } else { y += lh; y1 = y + getOffset().y; left = getStyleNode()->getFloat() == Float::LEFT ? eng.getXAtPosition(y1, y1 + lh) + x : eng.getX2AtPosition(y1, y1 + lh); w = eng.getWidthAtPosition(y1, y1 + lh, containing.content_.width); } } } setContentX(left); setContentY(top); auto& css_height = getStyleNode()->getHeight(); if(!css_height->isAuto()) { FixedPoint h = css_height->getLength().compute(containing.content_.height); //if(h > child_height_) { // /* apply overflow properties */ //} setContentHeight(h); } }